From 7cbd99ac1d2b4a0beffbaba29ea63d024ceff700 Mon Sep 17 00:00:00 2001 From: Windows classic samples Date: Wed, 11 May 2022 17:00:00 -0700 Subject: [PATCH] May 2022 samples * WASAPIRendering: Associating a Windows Audio Session API (WASAPI) stream with a window * FakeMenu: Demonstrate new menu theme parts and states * AcousticEchoCancellation: Setting a reference stream for acoustic echo cancellation * EventTracingEnumerateProviders: Enumerating providers with optional filters * CloudMirror: Providing status UI, also addresses #194, #221, #222 --- Samples/AcousticEchoCancellation/README.md | 52 ++ .../cpp/AECCapture.cpp | 173 +++++ .../AcousticEchoCancellation/cpp/AECCapture.h | 34 + .../cpp/AcousticEchoCancellation.cpp | 104 +++ .../cpp/AcousticEchoCancellation.sln | 31 + .../cpp/AcousticEchoCancellation.vcxproj | 169 +++++ .../AcousticEchoCancellation.vcxproj.filters | 33 + .../cpp/packages.config | 4 + .../CloudMirror/CloudMirror.vcxproj | 118 +--- .../CloudMirror/CloudMirror.vcxproj.filters | 21 + .../CloudMirror/CloudProviderRegistrar.cpp | 4 +- .../CloudProviderSyncRootWatcher.cpp | 27 +- .../CloudProviderSyncRootWatcher.h | 15 + .../CloudMirror/CloudMirror/ContextMenus.cpp | 47 +- .../CloudMirror/CloudMirror/ContextMenus.h | 2 + .../CloudMirror/CustomStateProvider.cpp | 8 +- .../CloudMirror/CustomStateProvider.h | 8 +- .../CloudMirror/CustomStateProvider.idl | 2 +- .../CloudMirror/DirectoryWatcher.cpp | 100 +-- .../CloudMirror/DirectoryWatcher.h | 10 +- Samples/CloudMirror/CloudMirror/Mirror.cpp | 16 +- .../CloudMirror/MyStatusUISource.cpp | 165 +++++ .../CloudMirror/MyStatusUISource.h | 35 + .../CloudMirror/MyStatusUISourceFactory.cpp | 21 + .../CloudMirror/MyStatusUISourceFactory.h | 31 + .../MyStorageProviderUICommand.cpp | 25 + .../CloudMirror/MyStorageProviderUICommand.h | 42 ++ .../CloudMirror/ProviderFolderLocations.cpp | 6 +- .../CloudMirror/CloudMirror/ShellServices.cpp | 37 +- Samples/CloudMirror/CloudMirror/StatusUI.idl | 14 + Samples/CloudMirror/CloudMirror/UriSource.h | 8 +- Samples/CloudMirror/CloudMirror/UriSource.idl | 4 +- Samples/CloudMirror/CloudMirror/Utilities.cpp | 7 +- .../CloudMirror/CloudMirror/packages.config | 2 +- Samples/CloudMirror/CloudMirror/stdafx.h | 11 +- .../CloudMirrorPackage.wapproj | 12 +- .../Images/darkTheme/CloudIconSynced.svg | 3 + .../Images/darkTheme/CloudIconSyncing.svg | 3 + .../Images/darkTheme/ProfileIcon.svg | 3 + .../Images/darkTheme/SettingsIcon.svg | 3 + .../Images/lightTheme/CloudIconSynced.svg | 3 + .../Images/lightTheme/CloudIconSyncing.svg | 3 + .../Images/lightTheme/ProfileIcon.svg | 3 + .../Images/lightTheme/SettingsIcon.svg | 3 + .../CloudMirrorPackage/Package.appxmanifest | 13 +- Samples/CloudMirror/README.md | 41 +- .../EnumerateProviders.cpp | 146 +++++ .../EnumerateProviders.sln | 31 + .../EnumerateProviders.vcxproj | 91 +++ .../EnumerateProviders.vcxproj.filters | 22 + .../EventTracingEnumerateProviders/README.md | 42 ++ Samples/WASAPIRendering/CmdLine.cpp | 149 +++++ Samples/WASAPIRendering/CmdLine.h | 31 + Samples/WASAPIRendering/README.md | 61 ++ Samples/WASAPIRendering/ToneGen.h | 72 +++ Samples/WASAPIRendering/WASAPIRenderer.cpp | 605 ++++++++++++++++++ Samples/WASAPIRendering/WASAPIRenderer.h | 120 ++++ Samples/WASAPIRendering/WASAPIRendering.cpp | 320 +++++++++ Samples/WASAPIRendering/WASAPIRendering.sln | 31 + .../WASAPIRendering/WASAPIRendering.vcxproj | 172 +++++ .../WASAPIRendering.vcxproj.filters | 51 ++ Samples/WASAPIRendering/packages.config | 4 + Samples/WASAPIRendering/pch.cpp | 5 + Samples/WASAPIRendering/pch.h | 24 + .../shell/legacysamples/fakemenu/Fakemenu.sln | 11 +- .../legacysamples/fakemenu/Fakemenu.vcproj | 324 ---------- .../legacysamples/fakemenu/Fakemenu.vcxproj | 84 +++ .../shell/legacysamples/fakemenu/README.md | 46 ++ .../shell/legacysamples/fakemenu/fakemenu.cpp | 420 ++++++++---- 69 files changed, 3666 insertions(+), 672 deletions(-) create mode 100644 Samples/AcousticEchoCancellation/README.md create mode 100644 Samples/AcousticEchoCancellation/cpp/AECCapture.cpp create mode 100644 Samples/AcousticEchoCancellation/cpp/AECCapture.h create mode 100644 Samples/AcousticEchoCancellation/cpp/AcousticEchoCancellation.cpp create mode 100644 Samples/AcousticEchoCancellation/cpp/AcousticEchoCancellation.sln create mode 100644 Samples/AcousticEchoCancellation/cpp/AcousticEchoCancellation.vcxproj create mode 100644 Samples/AcousticEchoCancellation/cpp/AcousticEchoCancellation.vcxproj.filters create mode 100644 Samples/AcousticEchoCancellation/cpp/packages.config create mode 100644 Samples/CloudMirror/CloudMirror/MyStatusUISource.cpp create mode 100644 Samples/CloudMirror/CloudMirror/MyStatusUISource.h create mode 100644 Samples/CloudMirror/CloudMirror/MyStatusUISourceFactory.cpp create mode 100644 Samples/CloudMirror/CloudMirror/MyStatusUISourceFactory.h create mode 100644 Samples/CloudMirror/CloudMirror/MyStorageProviderUICommand.cpp create mode 100644 Samples/CloudMirror/CloudMirror/MyStorageProviderUICommand.h create mode 100644 Samples/CloudMirror/CloudMirror/StatusUI.idl create mode 100644 Samples/CloudMirror/CloudMirrorPackage/Images/darkTheme/CloudIconSynced.svg create mode 100644 Samples/CloudMirror/CloudMirrorPackage/Images/darkTheme/CloudIconSyncing.svg create mode 100644 Samples/CloudMirror/CloudMirrorPackage/Images/darkTheme/ProfileIcon.svg create mode 100644 Samples/CloudMirror/CloudMirrorPackage/Images/darkTheme/SettingsIcon.svg create mode 100644 Samples/CloudMirror/CloudMirrorPackage/Images/lightTheme/CloudIconSynced.svg create mode 100644 Samples/CloudMirror/CloudMirrorPackage/Images/lightTheme/CloudIconSyncing.svg create mode 100644 Samples/CloudMirror/CloudMirrorPackage/Images/lightTheme/ProfileIcon.svg create mode 100644 Samples/CloudMirror/CloudMirrorPackage/Images/lightTheme/SettingsIcon.svg create mode 100644 Samples/EventTracingEnumerateProviders/EnumerateProviders.cpp create mode 100644 Samples/EventTracingEnumerateProviders/EnumerateProviders.sln create mode 100644 Samples/EventTracingEnumerateProviders/EnumerateProviders.vcxproj create mode 100644 Samples/EventTracingEnumerateProviders/EnumerateProviders.vcxproj.filters create mode 100644 Samples/EventTracingEnumerateProviders/README.md create mode 100644 Samples/WASAPIRendering/CmdLine.cpp create mode 100644 Samples/WASAPIRendering/CmdLine.h create mode 100644 Samples/WASAPIRendering/README.md create mode 100644 Samples/WASAPIRendering/ToneGen.h create mode 100644 Samples/WASAPIRendering/WASAPIRenderer.cpp create mode 100644 Samples/WASAPIRendering/WASAPIRenderer.h create mode 100644 Samples/WASAPIRendering/WASAPIRendering.cpp create mode 100644 Samples/WASAPIRendering/WASAPIRendering.sln create mode 100644 Samples/WASAPIRendering/WASAPIRendering.vcxproj create mode 100644 Samples/WASAPIRendering/WASAPIRendering.vcxproj.filters create mode 100644 Samples/WASAPIRendering/packages.config create mode 100644 Samples/WASAPIRendering/pch.cpp create mode 100644 Samples/WASAPIRendering/pch.h delete mode 100644 Samples/Win7Samples/winui/shell/legacysamples/fakemenu/Fakemenu.vcproj create mode 100644 Samples/Win7Samples/winui/shell/legacysamples/fakemenu/Fakemenu.vcxproj create mode 100644 Samples/Win7Samples/winui/shell/legacysamples/fakemenu/README.md diff --git a/Samples/AcousticEchoCancellation/README.md b/Samples/AcousticEchoCancellation/README.md new file mode 100644 index 00000000..6000bc9f --- /dev/null +++ b/Samples/AcousticEchoCancellation/README.md @@ -0,0 +1,52 @@ +--- +page_type: sample +languages: +- cpp +products: +- windows-api-win32 +name: Acoustic echo cancellation +urlFragment: AcousticEchoCancellation +description: Set the reference endpoint id for acoustic echo cancellation. +extendedZipContent: +- path: LICENSE + target: LICENSE +--- + +# Acoustic Echo Cancellation Sample + +This sample demonstrates the use of the IAcousticEchoCancellationControl::SetEchoCancellationRenderEndpoint method. + +The Acoustic Echo Cancellation APIs provide applications using communications streams the ability to customize +the reference stream of choice to use for echo cancellation. By default, the system uses the default render +device as the reference stream. + +The sample demonstrates how an application can check to see if the audio stream provides a way to configure +echo cancellation. If it does, the interface is used to configure the reference endpoint to be used for echo +cancellation on a communication stream by using the reference stream from a specific render endpoint. + +To use this sample, select a render endpoint from the list (by number), +and the sample use that render endpoint as the reference stream for echo cancellation. + +This sample requires Windows 11 build 22540 or later. + +Sample Language Implementations +=============================== + C++ + +To build the sample using the command prompt: +============================================= + + 1. Open the Command Prompt window and navigate to the directory. + 2. Type msbuild [Solution Filename] + +To build the sample using Visual Studio (preferred method): +================================================================ + + 1. Open Windows Explorer and navigate to the directory. + 2. Double-click the icon for the .sln (solution) file to open the file in Visual Studio. + 3. In the Build menu, select Build Solution. The application will be built in the default + \Debug or \Release directory + +To run the sample: +================= + Type AcousticEchoCancellation.exe at the command line. diff --git a/Samples/AcousticEchoCancellation/cpp/AECCapture.cpp b/Samples/AcousticEchoCancellation/cpp/AECCapture.cpp new file mode 100644 index 00000000..73203dd4 --- /dev/null +++ b/Samples/AcousticEchoCancellation/cpp/AECCapture.cpp @@ -0,0 +1,173 @@ +#include +#include +#include + +#include "AECCapture.h" + +#if !defined(NTDDI_WIN10_NI) || (NTDDI_VERSION < NTDDI_WIN10_NI) +#error This sample requires SDK version 22540 or higher. +#endif + +#define REFTIMES_PER_SEC 10000000 + +HRESULT CAECCapture::IsAcousticEchoCancellationEffectPresent(bool* result) +{ + *result = false; + + // IAudioEffectsManager requires build 22000 or higher. + wil::com_ptr_nothrow audioEffectsManager; + HRESULT hr = _audioClient->GetService(IID_PPV_ARGS(&audioEffectsManager)); + if (hr == E_NOINTERFACE) + { + // Audio effects manager is not supported, so clearly not present. + return S_OK; + } + + wil::unique_cotaskmem_array_ptr effects; + UINT32 numEffects; + RETURN_IF_FAILED(audioEffectsManager->GetAudioEffects(&effects, &numEffects)); + + for (UINT32 i = 0; i < numEffects; i++) + { + // Check for acoustic echo cancellation Audio Processing Object (APO) + if (effects[i].id == AUDIO_EFFECT_TYPE_ACOUSTIC_ECHO_CANCELLATION) + { + *result = true; + break; + } + } + return S_OK; +} + +HRESULT CAECCapture::InitializeCaptureClient() +{ + wil::com_ptr_nothrow enumerator; + RETURN_IF_FAILED(::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&enumerator))); + + wil::com_ptr_nothrow device; + RETURN_IF_FAILED(enumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &device)); + + RETURN_IF_FAILED(device->Activate(__uuidof(IAudioClient2), CLSCTX_INPROC_SERVER, NULL, (void**)&_audioClient)); + + // Set the category as communications. + AudioClientProperties clientProperties = {}; + clientProperties.cbSize = sizeof(AudioClientProperties); + clientProperties.eCategory = AudioCategory_Communications; + RETURN_IF_FAILED(_audioClient->SetClientProperties(&clientProperties)); + + wil::unique_cotaskmem_ptr wfxCapture; + RETURN_IF_FAILED(_audioClient->GetMixFormat(wil::out_param(wfxCapture))); + + REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC; + RETURN_IF_FAILED(_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + hnsRequestedDuration, + 0, + wfxCapture.get(), + NULL)); + + bool aecEffectPresent; + RETURN_IF_FAILED(IsAcousticEchoCancellationEffectPresent(&aecEffectPresent)); + + if (!aecEffectPresent) + { + wprintf(L"Warning: Capture stream is not echo cancelled.\n"); + + // An APO vendor can add code here to insert an in-app + // acoustic echo cancellation APO before starting the capture stream. + } + + wil::unique_cotaskmem_string deviceId; + RETURN_IF_FAILED(device->GetId(&deviceId)); + wprintf(L"Created communications stream on capture endpoint %ls\n", deviceId.get()); + + RETURN_IF_FAILED(_audioClient->GetService(IID_PPV_ARGS(&_captureClient))); + + return S_OK; +} + +HRESULT CAECCapture::RecordCommunicationsStream() +{ + DWORD mmcssTaskIndex = 0; + HANDLE mmcssTaskHandle = AvSetMmThreadCharacteristics(L"Audio", &mmcssTaskIndex); + RETURN_HR_IF(HRESULT_FROM_WIN32(GetLastError()), mmcssTaskHandle == 0); + + auto avRevertMmThreadCharacteristicsOnExit = wil::scope_exit([&]() + { + AvRevertMmThreadCharacteristics(mmcssTaskHandle); + }); + + wil::unique_event_nothrow bufferComplete; + RETURN_IF_FAILED(bufferComplete.create()); + RETURN_IF_FAILED(_audioClient->SetEventHandle(bufferComplete.get())); + + RETURN_IF_FAILED(_audioClient->Start()); + + wprintf(L"Started communications capture stream.\n"); + + HANDLE events[] = { _terminationEvent.get(), bufferComplete.get() }; + + while (WAIT_OBJECT_0 != WaitForMultipleObjects(ARRAYSIZE(events), events, FALSE, INFINITE)) + { + UINT32 packetLength = 0; + while (SUCCEEDED(_captureClient->GetNextPacketSize(&packetLength)) && packetLength > 0) + { + PBYTE buffer; + UINT32 numFramesRead; + DWORD flags = 0; + RETURN_IF_FAILED(_captureClient->GetBuffer(&buffer, &numFramesRead, &flags, nullptr, nullptr)); + + // At this point, the app can send the buffer to the capture pipeline. + // This program just discards the buffer without processing it. + + RETURN_IF_FAILED(_captureClient->ReleaseBuffer(numFramesRead)); + } + } + + RETURN_IF_FAILED(_audioClient->Stop()); + + return S_OK; +} + +HRESULT CAECCapture::StartCapture() try +{ + RETURN_IF_FAILED(_terminationEvent.create()); + + RETURN_IF_FAILED(InitializeCaptureClient()); + + _captureThread = std::thread( + [this]() + { + RecordCommunicationsStream(); + }); + + return S_OK; +} CATCH_RETURN() + +HRESULT CAECCapture::StopCapture() +{ + _terminationEvent.SetEvent(); + _captureThread.join(); + return S_OK; +} + +HRESULT CAECCapture::SetEchoCancellationRenderEndpoint(PCWSTR aecReferenceEndpointId) +{ + wil::com_ptr_nothrow aecControl; + HRESULT hr = _audioClient->GetService(IID_PPV_ARGS(&aecControl)); + + if (hr == E_NOINTERFACE) + { + // For this app, we ignore any failure to to control acoustic echo cancellation. + // (Treat as best effort.) + wprintf(L"Warning: Acoustic echo cancellation control is not available.\n"); + return S_OK; + } + + RETURN_IF_FAILED_MSG(hr, "_audioClient->GetService(IID_PPV_ARGS(&aecControl))"); + + // Call SetEchoCancellationRenderEndpoint to change the endpoint of the auxiliary input stream. + RETURN_IF_FAILED(aecControl->SetEchoCancellationRenderEndpoint(aecReferenceEndpointId)); + + return S_OK; +} diff --git a/Samples/AcousticEchoCancellation/cpp/AECCapture.h b/Samples/AcousticEchoCancellation/cpp/AECCapture.h new file mode 100644 index 00000000..57888f4c --- /dev/null +++ b/Samples/AcousticEchoCancellation/cpp/AECCapture.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +class CAECCapture : + public Microsoft::WRL::RuntimeClass< Microsoft::WRL::RuntimeClassFlags< Microsoft::WRL::ClassicCom >, Microsoft::WRL::FtmBase > +{ +public: + HRESULT StartCapture(); + HRESULT StopCapture(); + + HRESULT SetEchoCancellationRenderEndpoint(PCWSTR aecReferenceEndpointId); + +private: + HRESULT InitializeCaptureClient(); + HRESULT IsAcousticEchoCancellationEffectPresent(bool* result); + HRESULT RecordCommunicationsStream(); + + std::thread _captureThread; + wil::com_ptr_nothrow _captureClient; + wil::com_ptr_nothrow _audioClient; + + wil::unique_event_nothrow _terminationEvent; +}; diff --git a/Samples/AcousticEchoCancellation/cpp/AcousticEchoCancellation.cpp b/Samples/AcousticEchoCancellation/cpp/AcousticEchoCancellation.cpp new file mode 100644 index 00000000..47ef29b5 --- /dev/null +++ b/Samples/AcousticEchoCancellation/cpp/AcousticEchoCancellation.cpp @@ -0,0 +1,104 @@ +#include +#include +#include +#include "AECCapture.h" + +HRESULT GetRenderEndpointId(PWSTR* endpointId) +{ + *endpointId = nullptr; + + wil::com_ptr_nothrow deviceEnumerator; + RETURN_IF_FAILED(CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC, __uuidof(IMMDeviceEnumerator), (void**)&deviceEnumerator)); + + wil::com_ptr_nothrow spDeviceCollection; + RETURN_IF_FAILED(deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &spDeviceCollection)); + + UINT deviceCount; + RETURN_IF_FAILED(spDeviceCollection->GetCount(&deviceCount)); + + wprintf(L"0: system default\n"); + + for (UINT i = 0; i < deviceCount; i++) + { + // Get the device from the collection. + wil::com_ptr_nothrow device; + RETURN_IF_FAILED(spDeviceCollection->Item(i, &device)); + + // Get the device friendly name. + wil::com_ptr_nothrow properties; + RETURN_IF_FAILED(device->OpenPropertyStore(STGM_READ, &properties)); + wil::unique_prop_variant variant; + RETURN_IF_FAILED(properties->GetValue(PKEY_Device_FriendlyName, &variant)); + + wprintf(L"%d: %ls\n", i + 1, variant.pwszVal); + } + + wprintf(L"Choose a device to use as the acoustic echo cancellation render endpoint: "); + fflush(stdout); + + UINT index; + if (wscanf_s(L"%u", &index) != 1) + { + return HRESULT_FROM_WIN32(ERROR_CANCELLED); + } + if (index == 0) + { + // nullptr means "use the system default" + *endpointId = nullptr; + return S_OK; + } + + // Convert from 1-based index to 0-based index. + index = index - 1; + + if (index > deviceCount) + { + wprintf(L"Invalid choice.\n"); + return HRESULT_FROM_WIN32(ERROR_CANCELLED); + } + + // Get the chosen device from the collection. + wil::com_ptr_nothrow device; + RETURN_IF_FAILED(spDeviceCollection->Item(index, &device)); + + // Get and return the endpoint ID for that device. + RETURN_IF_FAILED(device->GetId(endpointId)); + + return S_OK; +} + +int wmain(int argc, wchar_t* argv[]) +{ + // Print diagnostic messages to the console for developer convenience. + wil::SetResultLoggingCallback([](wil::FailureInfo const& failure) noexcept + { + wchar_t message[1024]; + wil::GetFailureLogString(message, ARRAYSIZE(message), failure); + wprintf(L"Diagnostic message: %ls\n", message); + }); + + + RETURN_IF_FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED)); + wil::unique_couninitialize_call uninitialize; + + wil::unique_cotaskmem_string endpointId; + RETURN_IF_FAILED(GetRenderEndpointId(&endpointId)); + + CAECCapture aecCapture; + RETURN_IF_FAILED(aecCapture.StartCapture()); + + // Make sure we Stop capture even if an error occurs. + auto stop = wil::scope_exit([&] + { + aecCapture.StopCapture(); + }); + + RETURN_IF_FAILED(aecCapture.SetEchoCancellationRenderEndpoint(endpointId.get())); + + // Capture for 10 seconds. + wprintf(L"Capturing for 10 seconds...\n"); + Sleep(10000); + wprintf(L"Finished.\n"); + + return 0; +} diff --git a/Samples/AcousticEchoCancellation/cpp/AcousticEchoCancellation.sln b/Samples/AcousticEchoCancellation/cpp/AcousticEchoCancellation.sln new file mode 100644 index 00000000..97200be8 --- /dev/null +++ b/Samples/AcousticEchoCancellation/cpp/AcousticEchoCancellation.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30204.135 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AcousticEchoCancellation", "AcousticEchoCancellation.vcxproj", "{6E745655-513E-4713-B3AB-D6D3F62D7734}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6E745655-513E-4713-B3AB-D6D3F62D7734}.Debug|x64.ActiveCfg = Debug|x64 + {6E745655-513E-4713-B3AB-D6D3F62D7734}.Debug|x64.Build.0 = Debug|x64 + {6E745655-513E-4713-B3AB-D6D3F62D7734}.Debug|x86.ActiveCfg = Debug|Win32 + {6E745655-513E-4713-B3AB-D6D3F62D7734}.Debug|x86.Build.0 = Debug|Win32 + {6E745655-513E-4713-B3AB-D6D3F62D7734}.Release|x64.ActiveCfg = Release|x64 + {6E745655-513E-4713-B3AB-D6D3F62D7734}.Release|x64.Build.0 = Release|x64 + {6E745655-513E-4713-B3AB-D6D3F62D7734}.Release|x86.ActiveCfg = Release|Win32 + {6E745655-513E-4713-B3AB-D6D3F62D7734}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {64406148-8C03-42D2-8A2A-C14A03CFF8E3} + EndGlobalSection +EndGlobal diff --git a/Samples/AcousticEchoCancellation/cpp/AcousticEchoCancellation.vcxproj b/Samples/AcousticEchoCancellation/cpp/AcousticEchoCancellation.vcxproj new file mode 100644 index 00000000..43dfed82 --- /dev/null +++ b/Samples/AcousticEchoCancellation/cpp/AcousticEchoCancellation.vcxproj @@ -0,0 +1,169 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {6e745655-513e-4713-b3ab-d6d3f62d7734} + AcousticEchoCancellation + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + false + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Disabled + + + Console + true + true + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + F:\os2\os2\public\amd64fre + + + Console + true + true + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/Samples/AcousticEchoCancellation/cpp/AcousticEchoCancellation.vcxproj.filters b/Samples/AcousticEchoCancellation/cpp/AcousticEchoCancellation.vcxproj.filters new file mode 100644 index 00000000..c0f2f901 --- /dev/null +++ b/Samples/AcousticEchoCancellation/cpp/AcousticEchoCancellation.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + + + Header Files + + + + + + \ No newline at end of file diff --git a/Samples/AcousticEchoCancellation/cpp/packages.config b/Samples/AcousticEchoCancellation/cpp/packages.config new file mode 100644 index 00000000..26065b14 --- /dev/null +++ b/Samples/AcousticEchoCancellation/cpp/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Samples/CloudMirror/CloudMirror/CloudMirror.vcxproj b/Samples/CloudMirror/CloudMirror/CloudMirror.vcxproj index abc3eb74..09c713e6 100644 --- a/Samples/CloudMirror/CloudMirror/CloudMirror.vcxproj +++ b/Samples/CloudMirror/CloudMirror/CloudMirror.vcxproj @@ -1,6 +1,6 @@ - + Debug @@ -24,35 +24,24 @@ {93C711E5-7D66-45DC-9658-4323CE0892B0} Win32Proj CloudMirror - 10.0.17763.0 + 10.0 true - + Application - true - v141 - Unicode - - - Application - false - v141 - true + v142 + v143 Unicode - - Application + true - v141 - Unicode + true - - Application + false - v141 + false true - Unicode @@ -72,25 +61,12 @@ - - true - - - true - - - false - - - false - - + Use Level3 - Disabled true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + _CONSOLE;%(PreprocessorDefinitions) true stdcpp17 @@ -99,63 +75,23 @@ true kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;cldapi.lib;onecore.lib;onecoreuap.lib;%(AdditionalDependencies) - - + - Use - Level3 Disabled - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp17 + _DEBUG;%(PreprocessorDefinitions) - - Console - true - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;cldapi.lib;onecore.lib;onecoreuap.lib;%(AdditionalDependencies) - - + - Use - Level3 MaxSpeed true true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp17 + NDEBUG;%(PreprocessorDefinitions) - Console true true - true - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;cldapi.lib;onecore.lib;onecoreuap.lib;%(AdditionalDependencies) - - - - - - Use - Level3 - MaxSpeed - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp17 - - - Console - true - true - true - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;cldapi.lib;onecore.lib;onecoreuap.lib;%(AdditionalDependencies) @@ -165,11 +101,14 @@ + + + @@ -182,17 +121,17 @@ + + + - Create - Create - Create - Create + Create @@ -200,11 +139,9 @@ + - - - $(WindowsSDK_MetadataPathVersioned)\Windows.Storage.Provider.CloudFilesContract\3.0.0.0\Windows.Storage.Provider.CloudFilesContract.winmd @@ -212,15 +149,18 @@ true + + + - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/Samples/CloudMirror/CloudMirror/CloudMirror.vcxproj.filters b/Samples/CloudMirror/CloudMirror/CloudMirror.vcxproj.filters index 2a9bd8e9..e26dd326 100644 --- a/Samples/CloudMirror/CloudMirror/CloudMirror.vcxproj.filters +++ b/Samples/CloudMirror/CloudMirror/CloudMirror.vcxproj.filters @@ -72,6 +72,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + @@ -119,6 +128,15 @@ Source Files\Common + + Source Files\Provider + + + Source Files\Provider + + + Source Files\Provider + @@ -127,6 +145,9 @@ Source Files + + Source Files + diff --git a/Samples/CloudMirror/CloudMirror/CloudProviderRegistrar.cpp b/Samples/CloudMirror/CloudMirror/CloudProviderRegistrar.cpp index ec8e9a12..8e2cc18f 100644 --- a/Samples/CloudMirror/CloudMirror/CloudProviderRegistrar.cpp +++ b/Samples/CloudMirror/CloudMirror/CloudProviderRegistrar.cpp @@ -35,7 +35,9 @@ void CloudProviderRegistrar::RegisterWithShell() auto folder = winrt::StorageFolder::GetFolderFromPathAsync(ProviderFolderLocations::GetClientFolder()).get(); info.Path(folder); - info.DisplayNameResource(L"TestStorageProviderDisplayName"); + // The string can be in any form acceptable to SHLoadIndirectString. + info.DisplayNameResource(L"CloudMirror"); + // This icon is just for the sample. You should provide your own branded icon here info.IconResource(L"%SystemRoot%\\system32\\charmap.exe,0"); info.HydrationPolicy(winrt::StorageProviderHydrationPolicy::Full); diff --git a/Samples/CloudMirror/CloudMirror/CloudProviderSyncRootWatcher.cpp b/Samples/CloudMirror/CloudMirror/CloudProviderSyncRootWatcher.cpp index a8a34dfb..f5139175 100644 --- a/Samples/CloudMirror/CloudMirror/CloudProviderSyncRootWatcher.cpp +++ b/Samples/CloudMirror/CloudMirror/CloudProviderSyncRootWatcher.cpp @@ -23,8 +23,16 @@ // //=============================================================== +namespace winrt +{ + using namespace winrt::Windows::Foundation; + using namespace winrt::Windows::Storage::Provider; +} + DirectoryWatcher CloudProviderSyncRootWatcher::s_directoryWatcher; bool CloudProviderSyncRootWatcher::s_shutdownWatcher; +winrt::StorageProviderState CloudProviderSyncRootWatcher::s_state; +winrt::event> CloudProviderSyncRootWatcher::s_statusChanged; void CloudProviderSyncRootWatcher::WatchAndWait() { @@ -38,14 +46,13 @@ void CloudProviderSyncRootWatcher::WatchAndWait() { auto task = s_directoryWatcher.ReadChangesAsync(); - while (!task.is_done()) + while (task.Status() == winrt::AsyncStatus::Started) { Sleep(1000); if (s_shutdownWatcher) { s_directoryWatcher.Cancel(); - task.wait(); } } @@ -64,6 +71,10 @@ void CloudProviderSyncRootWatcher::WatchAndWait() void CloudProviderSyncRootWatcher::OnSyncRootFileChanges(_In_ std::list& changes) { + auto start = GetTickCount64(); + s_state = winrt::StorageProviderState::Syncing; + s_statusChanged(nullptr, nullptr); + for (auto path : changes) { wprintf(L"Processing change for %s\n", path.c_str()); @@ -89,6 +100,16 @@ void CloudProviderSyncRootWatcher::OnSyncRootFileChanges(_In_ std::list(3000 - elapsed)); + } + + s_state = winrt::StorageProviderState::InSync; + s_statusChanged(nullptr, nullptr); } void CloudProviderSyncRootWatcher::InitDirectoryWatcher() @@ -96,7 +117,7 @@ void CloudProviderSyncRootWatcher::InitDirectoryWatcher() // Set up a Directory Watcher on the client side to handle user's changing things there try { - s_directoryWatcher.Initalize(ProviderFolderLocations::GetClientFolder(), OnSyncRootFileChanges); + s_directoryWatcher.Initialize(ProviderFolderLocations::GetClientFolder(), OnSyncRootFileChanges); } catch (...) { diff --git a/Samples/CloudMirror/CloudMirror/CloudProviderSyncRootWatcher.h b/Samples/CloudMirror/CloudMirror/CloudProviderSyncRootWatcher.h index fe0273ad..b3742f15 100644 --- a/Samples/CloudMirror/CloudMirror/CloudProviderSyncRootWatcher.h +++ b/Samples/CloudMirror/CloudMirror/CloudProviderSyncRootWatcher.h @@ -12,11 +12,26 @@ class CloudProviderSyncRootWatcher static void WatchAndWait(); static BOOL WINAPI Stop(DWORD reason); + // These methods allow the StatusUISource to report on sync status. + static auto StatusChanged(winrt::Windows::Foundation::EventHandler const& handler) + { + return s_statusChanged.add(handler); + } + + static auto StatusChanged(winrt::event_token const& token) noexcept + { + return s_statusChanged.remove(token); + } + + static auto State() { return s_state; } + private: static void InitDirectoryWatcher(); static void OnSyncRootFileChanges(_In_ std::list& changes); static DirectoryWatcher s_directoryWatcher; static bool s_shutdownWatcher; + static winrt::Windows::Storage::Provider::StorageProviderState s_state; + static winrt::event> s_statusChanged; }; diff --git a/Samples/CloudMirror/CloudMirror/ContextMenus.cpp b/Samples/CloudMirror/CloudMirror/ContextMenus.cpp index 1c1a8dae..4ba0c8b0 100644 --- a/Samples/CloudMirror/CloudMirror/ContextMenus.cpp +++ b/Samples/CloudMirror/CloudMirror/ContextMenus.cpp @@ -8,6 +8,7 @@ #include "stdafx.h" #include "ContextMenus.h" #include +#include namespace winrt { @@ -33,54 +34,64 @@ IFACEMETHODIMP TestExplorerCommandHandler::GetFlags(_Out_ EXPCMDFLAGS* flags) return S_OK; } -IFACEMETHODIMP TestExplorerCommandHandler::Invoke(_In_opt_ IShellItemArray* selection, _In_opt_ IBindCtx*) -{ +winrt::fire_and_forget TestExplorerCommandHandler::InvokeAsync(_In_opt_ IShellItemArray* selection) +{ + winrt::com_ptr selectionCopy; + selectionCopy.copy_from(selection); + winrt::agile_ref agileSelectionCopy{ selectionCopy }; + + auto strongThis = get_strong(); // prevent destruction while coroutine is running + + co_await winrt::resume_background(); + try { - HWND hwnd = nullptr; - - if (_site) - { - // Get the HWND of the browser from the site to parent our message box to - winrt::com_ptr browser; - winrt::check_hresult(IUnknown_QueryService(_site.get(), SID_STopLevelBrowser, __uuidof(browser), browser.put_void())); - IUnknown_GetWindow(browser.get(), &hwnd); - } wprintf(L"Cloud Provider Command received\n"); + // If you want to show UI on invoke, do it in your own process. + // Do not use the Explorer window as an owner, because Explorer + // is not aware that the provider's context menu is still using it. + // // Set a new custom state on the selected files // - auto customProperties{ winrt::single_threaded_vector() }; winrt::StorageProviderItemProperty prop; prop.Id(3); prop.Value(L"Value3"); // This icon is just for the sample. You should provide your own branded icon here prop.IconResource(L"shell32.dll,-259"); - customProperties.Append(prop); DWORD count; - winrt::check_hresult(selection->GetCount(&count)); + winrt::check_hresult(agileSelectionCopy.get()->GetCount(&count)); for (DWORD i = 0; i < count; i++) { winrt::com_ptr shellItem; - winrt::check_hresult(selection->GetItemAt(i, shellItem.put())); + winrt::check_hresult(agileSelectionCopy.get()->GetItemAt(i, shellItem.put())); winrt::com_array fullPath; winrt::check_hresult(shellItem->GetDisplayName(SIGDN_FILESYSPATH, winrt::put_abi(fullPath))); - winrt::IStorageItem item = winrt::StorageFile::GetFileFromPathAsync(fullPath.data()).get(); - winrt::StorageProviderItemProperties::SetAsync(item, customProperties).get(); + auto fileAttributes = GetFileAttributes(fullPath.data()); + if (!(fileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + winrt::IStorageItem item = winrt::StorageFile::GetFileFromPathAsync(fullPath.data()).get(); + winrt::StorageProviderItemProperties::SetAsync(item, { prop }).get(); + } SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, static_cast(fullPath.data()), nullptr); } } catch (...) { - return winrt::to_hresult(); + wprintf(L"Cloud Provider Command failed\n"); } +} +IFACEMETHODIMP TestExplorerCommandHandler::Invoke(_In_opt_ IShellItemArray* selection, _In_opt_ IBindCtx*) +{ + TestExplorerCommandHandler::InvokeAsync(selection); + return S_OK; } diff --git a/Samples/CloudMirror/CloudMirror/ContextMenus.h b/Samples/CloudMirror/CloudMirror/ContextMenus.h index 169f27f9..7a318c19 100644 --- a/Samples/CloudMirror/CloudMirror/ContextMenus.h +++ b/Samples/CloudMirror/CloudMirror/ContextMenus.h @@ -6,6 +6,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved #pragma once +#include class __declspec(uuid("165cd069-d9c8-42b4-8e37-b6971afa4494")) TestExplorerCommandHandler : public winrt::implements @@ -29,4 +30,5 @@ class __declspec(uuid("165cd069-d9c8-42b4-8e37-b6971afa4494")) TestExplorerComma private: winrt::com_ptr _site; + winrt::fire_and_forget InvokeAsync(_In_opt_ IShellItemArray* selection); }; diff --git a/Samples/CloudMirror/CloudMirror/CustomStateProvider.cpp b/Samples/CloudMirror/CloudMirror/CustomStateProvider.cpp index 7aa00d4d..5da40c5a 100644 --- a/Samples/CloudMirror/CloudMirror/CustomStateProvider.cpp +++ b/Samples/CloudMirror/CloudMirror/CustomStateProvider.cpp @@ -1,4 +1,4 @@ -// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. @@ -20,7 +20,7 @@ namespace winrt::CloudMirror::implementation std::hash hashFunc; auto hash = hashFunc(itemPath.c_str()); - auto propertyVector{ winrt::single_threaded_vector() }; + std::vector properties; if ((hash & 0x1) != 0) { @@ -29,9 +29,9 @@ namespace winrt::CloudMirror::implementation itemProperty.Value(L"Value2"); // This icon is just for the sample. You should provide your own branded icon here itemProperty.IconResource(L"shell32.dll,-14"); - propertyVector.Append(itemProperty); + properties.push_back(std::move(itemProperty)); } - return propertyVector; + return winrt::single_threaded_vector(std::move(properties)); } } diff --git a/Samples/CloudMirror/CloudMirror/CustomStateProvider.h b/Samples/CloudMirror/CloudMirror/CustomStateProvider.h index 545406b2..d7200ddc 100644 --- a/Samples/CloudMirror/CloudMirror/CustomStateProvider.h +++ b/Samples/CloudMirror/CloudMirror/CustomStateProvider.h @@ -1,4 +1,4 @@ -// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. @@ -10,12 +10,10 @@ #include "CustomStateProvider.g.h" #include -/* f0c9de6c-6c76-44d7-a58e-579cdf7af263 */ -constexpr CLSID CLSID_CustomStateProvider = { 0xf0c9de6c, 0x6c76, 0x44d7, {0xa5, 0x8e, 0x57, 0x9c, 0xdf, 0x7a, 0xf2, 0x63} }; - namespace winrt::CloudMirror::implementation { - struct CustomStateProvider : CustomStateProviderT + struct __declspec(uuid("f0c9de6c-6c76-44d7-a58e-579cdf7af263")) + CustomStateProvider : CustomStateProviderT { CustomStateProvider() = default; diff --git a/Samples/CloudMirror/CloudMirror/CustomStateProvider.idl b/Samples/CloudMirror/CloudMirror/CustomStateProvider.idl index 66b1b685..7774a3f1 100644 --- a/Samples/CloudMirror/CloudMirror/CustomStateProvider.idl +++ b/Samples/CloudMirror/CloudMirror/CustomStateProvider.idl @@ -7,7 +7,7 @@ namespace CloudMirror { - runtimeclass CustomStateProvider : Windows.Storage.Provider.IStorageProviderItemPropertySource + runtimeclass CustomStateProvider : [default] Windows.Storage.Provider.IStorageProviderItemPropertySource { CustomStateProvider(); } diff --git a/Samples/CloudMirror/CloudMirror/DirectoryWatcher.cpp b/Samples/CloudMirror/CloudMirror/DirectoryWatcher.cpp index adc4f4d3..f172fad4 100644 --- a/Samples/CloudMirror/CloudMirror/DirectoryWatcher.cpp +++ b/Samples/CloudMirror/CloudMirror/DirectoryWatcher.cpp @@ -12,8 +12,8 @@ const size_t c_bufferSize = sizeof(FILE_NOTIFY_INFORMATION) * 100; // Run of the mill directory watcher to signal when user causes things to // happen in the client folder sync root -void DirectoryWatcher::Initalize( - _In_ PCWSTR path, +void DirectoryWatcher::Initialize( + _In_ PCWSTR path, _In_ std::function&)> callback) { _path = path; @@ -34,64 +34,72 @@ void DirectoryWatcher::Initalize( } } -concurrency::task DirectoryWatcher::ReadChangesAsync() +winrt::Windows::Foundation::IAsyncAction DirectoryWatcher::ReadChangesAsync() { - auto token = _cancellationTokenSource.get_token(); - return concurrency::create_task([this, token] + _readTask = ReadChangesInternalAsync(); + return _readTask; +} + +winrt::Windows::Foundation::IAsyncAction DirectoryWatcher::ReadChangesInternalAsync() +{ + co_await winrt::resume_background(); + + while (true) { - while (true) - { - DWORD returned; - winrt::check_bool(ReadDirectoryChangesW( - _dir.get(), - _notify.get(), - c_bufferSize, - TRUE, - FILE_NOTIFY_CHANGE_ATTRIBUTES, - &returned, - &_overlapped, - nullptr)); + DWORD returned; + winrt::check_bool(ReadDirectoryChangesW( + _dir.get(), + _notify.get(), + c_bufferSize, + TRUE, + FILE_NOTIFY_CHANGE_ATTRIBUTES, + &returned, + &_overlapped, + nullptr)); - DWORD transferred; - if (GetOverlappedResultEx(_dir.get(), &_overlapped, &transferred, 1000, FALSE)) + DWORD transferred; + if (!GetOverlappedResult(_dir.get(), &_overlapped, &transferred, TRUE)) + { + DWORD error = GetLastError(); + if (error != ERROR_OPERATION_ABORTED) { - std::list result; - FILE_NOTIFY_INFORMATION* next = _notify.get(); - while (next != nullptr) - { - std::wstring fullPath(_path); - fullPath.append(L"\\"); - fullPath.append(std::wstring_view(next->FileName, next->FileNameLength / sizeof(wchar_t))); - result.push_back(fullPath); - - if (next->NextEntryOffset) - { - next = reinterpret_cast(reinterpret_cast(next) + next->NextEntryOffset); - } - else - { - next = nullptr; - } - } - _callback(result); + throw winrt::hresult_error(HRESULT_FROM_WIN32(error)); } - else if (GetLastError() != WAIT_TIMEOUT) + break; + } + + std::list result; + FILE_NOTIFY_INFORMATION* next = _notify.get(); + while (next != nullptr) + { + std::wstring fullPath(_path); + fullPath.append(L"\\"); + fullPath.append(std::wstring_view(next->FileName, next->FileNameLength / sizeof(wchar_t))); + result.push_back(fullPath); + + if (next->NextEntryOffset) { - throw winrt::hresult_error(HRESULT_FROM_WIN32(GetLastError())); + next = reinterpret_cast(reinterpret_cast(next) + next->NextEntryOffset); } - else if (token.is_canceled()) + else { - wprintf(L"watcher cancel received\n"); - concurrency::cancel_current_task(); - return; + next = nullptr; } } - }, token); + _callback(result); + } + + wprintf(L"watcher exiting\n"); } void DirectoryWatcher::Cancel() { wprintf(L"Canceling watcher\n"); - _cancellationTokenSource.cancel(); + while (_readTask && (_readTask.Status() == winrt::AsyncStatus::Started) && + !CancelIoEx(_dir.get(), &_overlapped)) + { + // Raced against the thread loop. Try again. + Sleep(10); + } } diff --git a/Samples/CloudMirror/CloudMirror/DirectoryWatcher.h b/Samples/CloudMirror/CloudMirror/DirectoryWatcher.h index 5d7275e8..a13a7820 100644 --- a/Samples/CloudMirror/CloudMirror/DirectoryWatcher.h +++ b/Samples/CloudMirror/CloudMirror/DirectoryWatcher.h @@ -10,16 +10,18 @@ class DirectoryWatcher { public: - void Initalize(_In_ PCWSTR path, _In_ std::function&)> callback); - concurrency::task ReadChangesAsync(); + void Initialize(_In_ PCWSTR path, _In_ std::function&)> callback); + winrt::Windows::Foundation::IAsyncAction ReadChangesAsync(); void Cancel(); private: + winrt::Windows::Foundation::IAsyncAction ReadChangesInternalAsync(); + winrt::handle _dir; std::wstring _path; std::unique_ptr _notify; - OVERLAPPED _overlapped; - concurrency::cancellation_token_source _cancellationTokenSource; + OVERLAPPED _overlapped{}; + winrt::Windows::Foundation::IAsyncAction _readTask; std::function&)> _callback; }; diff --git a/Samples/CloudMirror/CloudMirror/Mirror.cpp b/Samples/CloudMirror/CloudMirror/Mirror.cpp index b815b23a..1dedcb72 100644 --- a/Samples/CloudMirror/CloudMirror/Mirror.cpp +++ b/Samples/CloudMirror/CloudMirror/Mirror.cpp @@ -9,11 +9,23 @@ int __cdecl wmain( INT argc, PWSTR argv[] ) { + winrt::init_apartment(); + + // Detect a common debugging error up front. + try + { + // If the program was launched incorrectly, this will throw. + (void)winrt::Windows::Storage::ApplicationData::Current(); + } + catch (...) + { + wprintf(L"This program should be launched from the Start menu, not from Visual Studio.\n"); + return 1; + } + wprintf(L"Press ctrl-C to stop gracefully\n"); wprintf(L"-------------------------------\n"); - winrt::init_apartment(); - auto returnCode{ 0 }; try diff --git a/Samples/CloudMirror/CloudMirror/MyStatusUISource.cpp b/Samples/CloudMirror/CloudMirror/MyStatusUISource.cpp new file mode 100644 index 00000000..9ad6fb45 --- /dev/null +++ b/Samples/CloudMirror/CloudMirror/MyStatusUISource.cpp @@ -0,0 +1,165 @@ +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved + +#include "stdafx.h" +#include "MyStatusUISource.h" +#include "MyStorageProviderUICommand.h" +#include +#include +#include + +namespace winrt +{ + using namespace ::winrt::Windows::Foundation; + using namespace ::winrt::Windows::Foundation::Collections; + using namespace ::winrt::Windows::UI::Core; + using namespace ::winrt::Windows::UI::ViewManagement; + using namespace ::winrt::Windows::Storage::Provider; +} + + +namespace winrt::CloudMirror::implementation +{ + StorageProviderStatusUI MyStatusUISource::GetStatusUI() + { + auto theme = L""; + + // GetStatusUI will be invoked on a theme & contrast changes from the client side + // We expect providers to provider alternative paths to the icons in this scenario + + auto settings = winrt::UISettings(); + auto foreground = settings.GetColorValue(UIColorType::Foreground); + bool isLightMode = !IsColorLight(foreground); + if (isLightMode) + { + theme = L"\\lightTheme"; + } + else + { + theme = L"\\darkTheme"; + } + + auto package = Windows::ApplicationModel::Package::Current(); + auto imagesPath = package.InstalledLocation().Path() + L"\\Images" + theme; + + auto ui = StorageProviderStatusUI(); + + // Required: Report the provider state. + StorageProviderState state = CloudProviderSyncRootWatcher::State(); + ui.ProviderState(state); + if (state == StorageProviderState::InSync) + { + // Brief description of sync state. + ui.ProviderStateLabel(L"Synced"); + // Optional icon. + ui.ProviderStateIcon(Uri{ imagesPath + L"\\CloudIconSynced.svg" }); + } + else + { + // Brief description of sync state. + ui.ProviderStateLabel(L"Syncing"); + ui.ProviderStateIcon(Uri{ imagesPath + L"\\CloudIconSyncing.svg" }); + } + + // Optional: Sync status command. + // If you do not set a SyncStatusCommand, or the command is Hidden, + // then the sync status banner is not shown. + IStorageProviderUICommand syncStatusCommand; + if (state == StorageProviderState::InSync) + { + syncStatusCommand = winrt::make( + L"Your files are in sync", + L"Your files are in sync with CloudMirror.", + Uri{ imagesPath + L"\\CloudIconSynced.svg" }, + StorageProviderUICommandState::Enabled); + } + else + { + syncStatusCommand = winrt::make( + L"Your files are syncing", + L"Your files are syncing with CloudMirror.", + Uri{ imagesPath + L"\\CloudIconSyncing.svg" }, + StorageProviderUICommandState::Enabled); + } + ui.SyncStatusCommand(syncStatusCommand); + + // Optional: Quota information. + // If you do not set a QuotaUI, then the quota section is not shown. + // This sample generates random quota information. + auto quotaUI = StorageProviderQuotaUI(); + auto percent = 100 * rand() / RAND_MAX; + int64_t gigabyte = 1073741824; + auto totalQuota = 1024 * gigabyte; // 1 terabyte + auto quotaUsed = percent * totalQuota / 100; + quotaUI.QuotaTotalInBytes(totalQuota); + quotaUI.QuotaUsedInBytes(quotaUsed); + quotaUI.QuotaUsedLabel(to_hstring(quotaUsed / gigabyte) + L" GB of 1 TB"); + // Optional: Set a custom color for the quota bar. + if (percent > 90) + { + quotaUI.QuotaUsedColor(Windows::UI::Colors::Red()); + } + + ui.QuotaUI(quotaUI); + + // Optional: More info + // We display more info if the user is running out of storage. + if (percent > 80) + { + auto moreInfoUI = StorageProviderMoreInfoUI(); + moreInfoUI.Message(L"Your CloudMirror is getting full. Get more storage so you won't run out."); + moreInfoUI.Command(winrt::make( + L"Explore storage plans", + L"Explore CloudMirror storage plans.", + Uri{ imagesPath + L"\\CloudIconSynced.svg" }, + StorageProviderUICommandState::Enabled)); + ui.MoreInfoUI(moreInfoUI); + } + + // Optional: The primary command is shown as a textual button. + auto primaryCommand = winrt::make( + L"View online", + L"View CloudMirror online.", + Uri{ imagesPath + L"\\CloudIconSynced.svg" }, + StorageProviderUICommandState::Enabled); + ui.ProviderPrimaryCommand(primaryCommand); + + // Optional: Secondary commands are shown as icons. + auto secondaryCommand1 = winrt::make( + L"View profile", + L"View your CloudMirror user profile", + Uri{ imagesPath + L"\\ProfileIcon.svg" }, + StorageProviderUICommandState::Enabled); + auto secondaryCommand2 = winrt::make( + L"Settings", + L"View your CloudMirror settings", + Uri{ imagesPath + L"\\SettingsIcon.svg" }, + StorageProviderUICommandState::Enabled); + ui.ProviderSecondaryCommands({ secondaryCommand1, secondaryCommand2 }); + + return ui; + } + + // The provider raises the StatusUIChanged event when the status has changed. + // Upon learning that the status has changed, the system will call GetStatusUI to + // get the new status. + winrt::event_token MyStatusUISource::StatusUIChanged(winrt::Windows::Foundation::TypedEventHandler const& handler) + { + return CloudProviderSyncRootWatcher::StatusChanged([weak = get_weak(), agile_handler = agile_ref(handler)](auto&&, auto&&) + { + if (auto strong = weak.get()) + { + agile_handler.get()(*strong, nullptr); + } + }); + }; + + void MyStatusUISource::StatusUIChanged(winrt::event_token const& token) noexcept + { + return CloudProviderSyncRootWatcher::StatusChanged(token); + } +} diff --git a/Samples/CloudMirror/CloudMirror/MyStatusUISource.h b/Samples/CloudMirror/CloudMirror/MyStatusUISource.h new file mode 100644 index 00000000..abcdf935 --- /dev/null +++ b/Samples/CloudMirror/CloudMirror/MyStatusUISource.h @@ -0,0 +1,35 @@ +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved + +#pragma once + +namespace winrt::CloudMirror::implementation +{ + struct MyStatusUISource : implements + { + MyStatusUISource() = default; + + Windows::Storage::Provider::StorageProviderStatusUI GetStatusUI(); + + // event StatusUIChanged + winrt::event_token StatusUIChanged(winrt::Windows::Foundation::TypedEventHandler const& handler); + void StatusUIChanged(winrt::event_token const& token) noexcept; + + private: + + // This is a simple weighting of R, G, and B to account for perceptual brightness. + // It is more than enough to determine lightmode -vs- darkmode (because the colors + // are nearly black or nearly white), but it should not be taken + // as a canonical formula for perceptual brightness + + // This is a hacky solution that we do not encourage providers to use for detecting theme changes + inline bool IsColorLight(Windows::UI::Color& clr) + { + return (((5 * clr.G) + (2 * clr.R) + clr.B) > (8 * 128)); + } + }; +} diff --git a/Samples/CloudMirror/CloudMirror/MyStatusUISourceFactory.cpp b/Samples/CloudMirror/CloudMirror/MyStatusUISourceFactory.cpp new file mode 100644 index 00000000..0c3c2fc3 --- /dev/null +++ b/Samples/CloudMirror/CloudMirror/MyStatusUISourceFactory.cpp @@ -0,0 +1,21 @@ +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved + +#include "stdafx.h" +#include "MyStatusUISourceFactory.h" +#include "MyStatusUISource.h" + +using namespace winrt::Windows::Storage::Provider; + +namespace winrt::CloudMirror::implementation +{ + winrt::IStorageProviderStatusUISource MyStatusUISourceFactory::GetStatusUISource(hstring const& /*syncRootId*/) + { + // A real sync engine should use the syncRootId to verify the correct user account to create the StatusUISource for. + return winrt::make(); + } +} diff --git a/Samples/CloudMirror/CloudMirror/MyStatusUISourceFactory.h b/Samples/CloudMirror/CloudMirror/MyStatusUISourceFactory.h new file mode 100644 index 00000000..d2b3370c --- /dev/null +++ b/Samples/CloudMirror/CloudMirror/MyStatusUISourceFactory.h @@ -0,0 +1,31 @@ +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved + +#pragma once + +#include "MyStatusUISourceFactory.g.h" + +namespace winrt::CloudMirror::implementation +{ + struct __declspec(uuid("b1d8ef74-822d-401a-a14a-25f45b1f70b7")) + MyStatusUISourceFactory : MyStatusUISourceFactoryT + { + MyStatusUISourceFactory() = default; + + winrt::Windows::Storage::Provider::IStorageProviderStatusUISource GetStatusUISource(hstring const& syncRootId); + + private: + winrt::Windows::Storage::Provider::IStorageProviderStatusUISource m_statusUISource{ nullptr }; + }; +} + +namespace winrt::CloudMirror::factory_implementation +{ + struct MyStatusUISourceFactory : MyStatusUISourceFactoryT + { + }; +} diff --git a/Samples/CloudMirror/CloudMirror/MyStorageProviderUICommand.cpp b/Samples/CloudMirror/CloudMirror/MyStorageProviderUICommand.cpp new file mode 100644 index 00000000..967bbd58 --- /dev/null +++ b/Samples/CloudMirror/CloudMirror/MyStorageProviderUICommand.cpp @@ -0,0 +1,25 @@ +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved + +#include "stdafx.h" +#include "MyStorageProviderUICommand.h" + +namespace winrt +{ + using namespace ::winrt::Windows::Storage::Provider; +} + +namespace winrt::CloudMirror::implementation +{ + void MyStorageProviderUICommand::Invoke() + { + // Your provider should perform the command the user requested. + // This sample just prints a message. + wprintf(L"Execute the command \"%ls\"\n", m_label.c_str()); + } +} + diff --git a/Samples/CloudMirror/CloudMirror/MyStorageProviderUICommand.h b/Samples/CloudMirror/CloudMirror/MyStorageProviderUICommand.h new file mode 100644 index 00000000..b0763414 --- /dev/null +++ b/Samples/CloudMirror/CloudMirror/MyStorageProviderUICommand.h @@ -0,0 +1,42 @@ +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved + +#pragma once + +namespace winrt::CloudMirror::implementation +{ + struct MyStorageProviderUICommand : implements + { + MyStorageProviderUICommand(hstring const& label, hstring const& description, Windows::Foundation::Uri const& iconUri, Windows::Storage::Provider::StorageProviderUICommandState const& state) + : m_label(label), m_description(description), m_iconUri(iconUri), m_state(state) + {} + + // Label: The text to show. + auto Label() const { return m_label; } + + // Description: Detailed information. + auto Description() const { return m_description; } + + // Icon: The icon to show. + auto Icon() const { return m_iconUri; } + + // State: + // * Enabled shows the button in an enabled state. + // * Disabled shows the button in a disabled state. + // * Hidden hides the button entirely. + auto State() const { return m_state; } + + // Invoke: The system calls this method when the user executes the command. + void Invoke(); + + private: + hstring m_label; + hstring m_description; + Windows::Foundation::Uri m_iconUri; + Windows::Storage::Provider::StorageProviderUICommandState m_state = Windows::Storage::Provider::StorageProviderUICommandState::Enabled; + }; +} diff --git a/Samples/CloudMirror/CloudMirror/ProviderFolderLocations.cpp b/Samples/CloudMirror/CloudMirror/ProviderFolderLocations.cpp index d3928be7..b9f62efa 100644 --- a/Samples/CloudMirror/CloudMirror/ProviderFolderLocations.cpp +++ b/Samples/CloudMirror/CloudMirror/ProviderFolderLocations.cpp @@ -68,8 +68,10 @@ std::wstring ProviderFolderLocations::PromptForFolderPath(_In_ PCWSTR title) { auto lastLocation = winrt::unbox_value(settings.Lookup(title)); winrt::com_ptr lastItem; - winrt::check_hresult(SHCreateItemFromParsingName(lastLocation.c_str(), nullptr, __uuidof(lastItem), lastItem.put_void())); - winrt::check_hresult(fileOpen->SetFolder(lastItem.get())); + if (SUCCEEDED(SHCreateItemFromParsingName(lastLocation.c_str(), nullptr, __uuidof(lastItem), lastItem.put_void()))) + { + winrt::check_hresult(fileOpen->SetFolder(lastItem.get())); + } } try diff --git a/Samples/CloudMirror/CloudMirror/ShellServices.cpp b/Samples/CloudMirror/CloudMirror/ShellServices.cpp index 750fb8e9..6cf93ea2 100644 --- a/Samples/CloudMirror/CloudMirror/ShellServices.cpp +++ b/Samples/CloudMirror/CloudMirror/ShellServices.cpp @@ -12,6 +12,8 @@ #include "ContextMenus.h" #include "CustomStateProvider.h" #include "UriSource.h" +#include "MyStatusUISourceFactory.h" +#include "MyStorageProviderUICommand.h" //=============================================================== // ShellServices @@ -27,27 +29,29 @@ // //=============================================================== +namespace +{ + template + DWORD make_and_register_class_object() + { + DWORD cookie; + auto factory = winrt::make>(); + winrt::check_hresult(CoRegisterClassObject(__uuidof(T), factory.get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &cookie)); + return cookie; + } +} void ShellServices::InitAndStartServiceTask() { - auto task = concurrency::create_task([]() + auto task = std::thread([]() { winrt::init_apartment(winrt::apartment_type::single_threaded); - winrt::check_hresult(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)); - - DWORD cookie; - auto thumbnailProviderClassFactory = winrt::make>(); - winrt::check_hresult(CoRegisterClassObject(__uuidof(ThumbnailProvider), thumbnailProviderClassFactory.get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &cookie)); - - auto contextMenuClassFactory = winrt::make>(); - winrt::check_hresult(CoRegisterClassObject(__uuidof(TestExplorerCommandHandler), contextMenuClassFactory.get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &cookie)); - - auto customStateProvider = winrt::make>(); - winrt::check_hresult(CoRegisterClassObject(CLSID_CustomStateProvider, customStateProvider.get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &cookie)); - - auto uriSource = winrt::make>(); - winrt::check_hresult(CoRegisterClassObject(CLSID_UriSource, uriSource.get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &cookie)); + make_and_register_class_object(); + make_and_register_class_object(); + make_and_register_class_object(); + make_and_register_class_object(); + make_and_register_class_object(); winrt::handle dummyEvent(CreateEvent(nullptr, FALSE, FALSE, nullptr)); if (!dummyEvent) @@ -58,4 +62,5 @@ void ShellServices::InitAndStartServiceTask() HANDLE temp = dummyEvent.get(); CoWaitForMultipleHandles(COWAIT_DISPATCH_CALLS, INFINITE, 1, &temp, &index); }); -} \ No newline at end of file + task.detach(); +} diff --git a/Samples/CloudMirror/CloudMirror/StatusUI.idl b/Samples/CloudMirror/CloudMirror/StatusUI.idl new file mode 100644 index 00000000..1d8e264d --- /dev/null +++ b/Samples/CloudMirror/CloudMirror/StatusUI.idl @@ -0,0 +1,14 @@ +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved + +namespace CloudMirror +{ + runtimeclass MyStatusUISourceFactory : [default] Windows.Storage.Provider.IStorageProviderStatusUISourceFactory + { + MyStatusUISourceFactory(); + } +} diff --git a/Samples/CloudMirror/CloudMirror/UriSource.h b/Samples/CloudMirror/CloudMirror/UriSource.h index 55f5c6e4..10656a22 100644 --- a/Samples/CloudMirror/CloudMirror/UriSource.h +++ b/Samples/CloudMirror/CloudMirror/UriSource.h @@ -1,4 +1,4 @@ -// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. @@ -9,12 +9,10 @@ #include "UriSource.g.h" -// 97961bcb-601c-4950-927c-43b9319c7217 -constexpr CLSID CLSID_UriSource = { 0x97961bcb, 0x601c, 0x4950, {0x92, 0x7c, 0x43, 0xb9, 0x31, 0x9c, 0x72, 0x17} }; - namespace winrt::CloudMirror::implementation { - struct UriSource : UriSourceT + struct __declspec(uuid("97961bcb-601c-4950-927c-43b9319c7217")) + UriSource : UriSourceT { UriSource() = default; diff --git a/Samples/CloudMirror/CloudMirror/UriSource.idl b/Samples/CloudMirror/CloudMirror/UriSource.idl index d910aa95..79f76ba6 100644 --- a/Samples/CloudMirror/CloudMirror/UriSource.idl +++ b/Samples/CloudMirror/CloudMirror/UriSource.idl @@ -7,8 +7,8 @@ namespace CloudMirror { - runtimeclass UriSource : Windows.Storage.Provider.IStorageProviderUriSource + runtimeclass UriSource : [default] Windows.Storage.Provider.IStorageProviderUriSource { UriSource(); } -} \ No newline at end of file +} diff --git a/Samples/CloudMirror/CloudMirror/Utilities.cpp b/Samples/CloudMirror/CloudMirror/Utilities.cpp index 9307c405..3a3ff2d2 100644 --- a/Samples/CloudMirror/CloudMirror/Utilities.cpp +++ b/Samples/CloudMirror/CloudMirror/Utilities.cpp @@ -116,11 +116,8 @@ void Utilities::ApplyCustomStateToPlaceholderFile(PCWSTR path, PCWSTR filename, fullPath.append(L"\\"); fullPath.append(filename); - auto customProperties{ winrt::single_threaded_vector() }; - customProperties.Append(prop); - winrt::IStorageItem item = winrt::StorageFile::GetFileFromPathAsync(fullPath).get(); - winrt::StorageProviderItemProperties::SetAsync(item, customProperties).get(); + winrt::StorageProviderItemProperties::SetAsync(item, { prop }).get(); } catch (...) { @@ -128,4 +125,4 @@ void Utilities::ApplyCustomStateToPlaceholderFile(PCWSTR path, PCWSTR filename, // otherwise the exception will get rethrown and this method will crash out as it should wprintf(L"Failed to set custom state with %08x\n", static_cast(winrt::to_hresult())); } -} \ No newline at end of file +} diff --git a/Samples/CloudMirror/CloudMirror/packages.config b/Samples/CloudMirror/CloudMirror/packages.config index f70b2342..03e2a585 100644 --- a/Samples/CloudMirror/CloudMirror/packages.config +++ b/Samples/CloudMirror/CloudMirror/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/Samples/CloudMirror/CloudMirror/stdafx.h b/Samples/CloudMirror/CloudMirror/stdafx.h index 27dda552..6b0e62e5 100644 --- a/Samples/CloudMirror/CloudMirror/stdafx.h +++ b/Samples/CloudMirror/CloudMirror/stdafx.h @@ -14,8 +14,14 @@ #include "targetver.h" +#ifndef NTDDI_WIN10_NI +#error This sample requires the Windows SDK version 10.0.22598.0 or higher. +#endif + #include #include +#include +#define WIN32_NO_STATUS #include #include #include @@ -24,11 +30,12 @@ #include #include #include -#include #include +#include +#include #include #include -#include +#include #include namespace winrt { diff --git a/Samples/CloudMirror/CloudMirrorPackage/CloudMirrorPackage.wapproj b/Samples/CloudMirror/CloudMirrorPackage/CloudMirrorPackage.wapproj index d1d52552..47dfd938 100644 --- a/Samples/CloudMirror/CloudMirrorPackage/CloudMirrorPackage.wapproj +++ b/Samples/CloudMirror/CloudMirrorPackage/CloudMirrorPackage.wapproj @@ -35,8 +35,8 @@ 1fab5d02-237c-4b9c-b820-2eb7c3b6e63a - 10.0.17763.0 - 10.0.17134.0 + 10.0.22598.0 + 10.0.18362.0 en-US CloudMirrorPackage_TemporaryKey.pfx ..\CloudMirror\CloudMirror.vcxproj @@ -77,6 +77,14 @@ + + + + + + + + diff --git a/Samples/CloudMirror/CloudMirrorPackage/Images/darkTheme/CloudIconSynced.svg b/Samples/CloudMirror/CloudMirrorPackage/Images/darkTheme/CloudIconSynced.svg new file mode 100644 index 00000000..99c651b6 --- /dev/null +++ b/Samples/CloudMirror/CloudMirrorPackage/Images/darkTheme/CloudIconSynced.svg @@ -0,0 +1,3 @@ + + + diff --git a/Samples/CloudMirror/CloudMirrorPackage/Images/darkTheme/CloudIconSyncing.svg b/Samples/CloudMirror/CloudMirrorPackage/Images/darkTheme/CloudIconSyncing.svg new file mode 100644 index 00000000..f22eb3f3 --- /dev/null +++ b/Samples/CloudMirror/CloudMirrorPackage/Images/darkTheme/CloudIconSyncing.svg @@ -0,0 +1,3 @@ + + + diff --git a/Samples/CloudMirror/CloudMirrorPackage/Images/darkTheme/ProfileIcon.svg b/Samples/CloudMirror/CloudMirrorPackage/Images/darkTheme/ProfileIcon.svg new file mode 100644 index 00000000..f4484717 --- /dev/null +++ b/Samples/CloudMirror/CloudMirrorPackage/Images/darkTheme/ProfileIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/Samples/CloudMirror/CloudMirrorPackage/Images/darkTheme/SettingsIcon.svg b/Samples/CloudMirror/CloudMirrorPackage/Images/darkTheme/SettingsIcon.svg new file mode 100644 index 00000000..2677284e --- /dev/null +++ b/Samples/CloudMirror/CloudMirrorPackage/Images/darkTheme/SettingsIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/Samples/CloudMirror/CloudMirrorPackage/Images/lightTheme/CloudIconSynced.svg b/Samples/CloudMirror/CloudMirrorPackage/Images/lightTheme/CloudIconSynced.svg new file mode 100644 index 00000000..6afbf034 --- /dev/null +++ b/Samples/CloudMirror/CloudMirrorPackage/Images/lightTheme/CloudIconSynced.svg @@ -0,0 +1,3 @@ + + + diff --git a/Samples/CloudMirror/CloudMirrorPackage/Images/lightTheme/CloudIconSyncing.svg b/Samples/CloudMirror/CloudMirrorPackage/Images/lightTheme/CloudIconSyncing.svg new file mode 100644 index 00000000..f3866f56 --- /dev/null +++ b/Samples/CloudMirror/CloudMirrorPackage/Images/lightTheme/CloudIconSyncing.svg @@ -0,0 +1,3 @@ + + + diff --git a/Samples/CloudMirror/CloudMirrorPackage/Images/lightTheme/ProfileIcon.svg b/Samples/CloudMirror/CloudMirrorPackage/Images/lightTheme/ProfileIcon.svg new file mode 100644 index 00000000..8016ce12 --- /dev/null +++ b/Samples/CloudMirror/CloudMirrorPackage/Images/lightTheme/ProfileIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/Samples/CloudMirror/CloudMirrorPackage/Images/lightTheme/SettingsIcon.svg b/Samples/CloudMirror/CloudMirrorPackage/Images/lightTheme/SettingsIcon.svg new file mode 100644 index 00000000..a517d55e --- /dev/null +++ b/Samples/CloudMirror/CloudMirrorPackage/Images/lightTheme/SettingsIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/Samples/CloudMirror/CloudMirrorPackage/Package.appxmanifest b/Samples/CloudMirror/CloudMirrorPackage/Package.appxmanifest index 0190f5b1..b8f25f16 100644 --- a/Samples/CloudMirror/CloudMirrorPackage/Package.appxmanifest +++ b/Samples/CloudMirror/CloudMirrorPackage/Package.appxmanifest @@ -1,4 +1,4 @@ - + + xmlns:cloudfiles2="http://schemas.microsoft.com/appx/manifest/cloudfiles/windows10/2" + IgnorableNamespaces="uap mp rescap desktop3 desktop4 cloudfiles2"> - + @@ -49,6 +50,7 @@ + @@ -69,6 +71,9 @@ + + + @@ -79,4 +84,4 @@ - \ No newline at end of file + diff --git a/Samples/CloudMirror/README.md b/Samples/CloudMirror/README.md index dca46c54..db34c546 100644 --- a/Samples/CloudMirror/README.md +++ b/Samples/CloudMirror/README.md @@ -27,10 +27,11 @@ The following functionality is implemented: * Providing thumbnails for the file placeholders. * adding a custom entry to the context menu when the user clicks on a file in the Sync Root. * Supplying the URI of the cloud location of a file. +* Providing custom provider status UI. **Note** The Windows-classic-samples repo contains a variety of code samples that exercise the various programming models, platforms, features, and components available in Windows and/or Windows Server. This repo provides a Visual Studio solution (SLN) file for each sample, along with the source files, assets, resources, and metadata needed to compile and run the sample. For more info about the programming models, platforms, languages, and APIs demonstrated in these samples, check out the documentation on the [Windows Dev Center](https://dev.windows.com). This sample is provided as-is in order to indicate or demonstrate the functionality of the programming models and feature APIs for Windows and/or Windows Server. -This sample was created for Windows 10 Version 1809 using Visual Studio 2017 using the Windows SDK 10.0.17763.0, but in many cases it will run unaltered using later versions. Please provide feedback on this sample! +This sample was created for Windows 10 Version 1809 using Visual Studio and the Windows SDK 10.0.22598.0, but in many cases it will run unaltered using later versions. Please provide feedback on this sample! To get a copy of Windows, go to [Downloads and tools](http://go.microsoft.com/fwlink/p/?linkid=301696). @@ -44,7 +45,6 @@ To get a copy of Visual Studio, go to [Visual Studio Downloads](http://go.micros [**UWP StorageProvider**](https://docs.microsoft.com/en-us/uwp/api/windows.storage.provider) - ## Related technologies [**Cloud files API**](https://docs.microsoft.com/en-us/windows/desktop/cfApi/cloud-files-api-portal) @@ -67,8 +67,8 @@ To build this sample, open the solution (*.sln*) file titled *CloudMirror.sln* f ## Run the sample -1. To run this sample after building it, press **F5**. A **Directory Picker** dialog appears. -1. Indicate the physical folder on your dev machine that holds a representation of the "cloud". After picking that folder, a second **Directory Picker** dialog appears. +1. To run this sample after building and deploying it, run the program **from the Start menu**. +1. Upon launch, a **Directory Picker** dialog appears. Indicate the physical folder on your dev machine that holds a representation of the "cloud". After picking that folder, a second **Directory Picker** dialog appears. 1. Indicate the location of your sync root (client), which is populated with placeholders of the files in the "cloud". Once you dismiss that picker, a console window appears with status messages, and the sync root appears in File Explorer. 1. Play around with the files in the sync root, making them available on the machine and freeing up space. Notice the custom state icons. Watch hydration show progress bars. 1. Press **CTRL**+**C** in the console window to gracefully exit. @@ -76,3 +76,36 @@ To build this sample, open the solution (*.sln*) file titled *CloudMirror.sln* f The sample unregisters the sync root when it closes or crashes. This behavior is for demonstration purposes. A real-world provider would remain registered, so that when the user selects the sync root in File Explorer, the provider app restarts. Automatic restarting is not desirable for a demonstration, however. **NOTE**: If you hydrated some files while testing and then shut down the sample, you should delete everything from the sync root folder before re-running the sample. Otherwise the sample will behave unpredictably. + +## Debug the sample + +1. To debug the sample, build and deploy it, and then run the program **from the Start menu**. +1. From Visual Studio, go to Debug, Attach to Process, and select the CloudMirror process. + +## Open source licenses + +The SVG icons in this sample were obtained from +Microsoft [Fluent UI System Icons](https://github.com/microsoft/fluentui-system-icons), +which is subject to the following license: + +> MIT License +> +> Copyright (c) 2020 Microsoft Corporation +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. diff --git a/Samples/EventTracingEnumerateProviders/EnumerateProviders.cpp b/Samples/EventTracingEnumerateProviders/EnumerateProviders.cpp new file mode 100644 index 00000000..303eb6ca --- /dev/null +++ b/Samples/EventTracingEnumerateProviders/EnumerateProviders.cpp @@ -0,0 +1,146 @@ +//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +//// PARTICULAR PURPOSE. +//// +//// Copyright (c) Microsoft Corporation. All rights reserved + +#include +#include +#include +#include +#include + +#pragma comment(lib, "tdh.lib") + +#define MAX_GUID_SIZE 39 + +static PCWSTR GetSchemaSourceName(ULONG schemaSource) +{ + switch (schemaSource) + { + case 0: return L"XML manifest"; + case 1: return L"WMI MOF class"; + default: return L"unknown"; + } +} + +static void ShowProviderInfo(PROVIDER_ENUMERATION_INFO* pEnum) +{ + for (DWORD i = 0; i < pEnum->NumberOfProviders; i++) + { + WCHAR stringGuid[MAX_GUID_SIZE]; + (void)StringFromGUID2(pEnum->TraceProviderInfoArray[i].ProviderGuid, stringGuid, ARRAYSIZE(stringGuid)); + + // Provider information is in pEnum->TraceProviderInfoArray[i]. + wprintf(L"Provider name: %ls\nProvider GUID: %ls\nSource: %u (%ls)\n\n", + (LPWSTR)((PBYTE)(pEnum)+pEnum->TraceProviderInfoArray[i].ProviderNameOffset), + stringGuid, + pEnum->TraceProviderInfoArray[i].SchemaSource, + GetSchemaSourceName(pEnum->TraceProviderInfoArray[i].SchemaSource)); + } +} + +#if defined(NTDDI_WIN10_MN) && (NTDDI_VERSION >= NTDDI_WIN10_MN) +void TdhEnumerateProvidersForDecodingSourceSample() +{ + DWORD status; + std::unique_ptr manifestProvidersBuffer; + DWORD manifestProvidersBufferSize = 0; + + // Available in Windows 10 build 20348 or later. + // Retrieve providers registered via manifest files with TdhEnumerateProvidersForDecodingSource. + // Allocate the required buffer and call TdhEnumerateProvidersForDecodingSource. + // The list of providers can change between the time you retrieved the required + // buffer size and the time you enumerated the providers, so call + // TdhEnumerateProvidersForDecodingSource in a loop until the function does + // not return ERROR_INSUFFICIENT_BUFFER. + for (;;) + { + // Note that the only supported decoding sources are DecodingSourceXMLFile + // and DecodingSourceWbem. This sample uses DecodingSourceXMLFile. + status = TdhEnumerateProvidersForDecodingSource( + DecodingSourceXMLFile, + reinterpret_cast(manifestProvidersBuffer.get()), + manifestProvidersBufferSize, + &manifestProvidersBufferSize); + + if (status != ERROR_INSUFFICIENT_BUFFER) + { + break; + } + + manifestProvidersBuffer.reset(new(std::nothrow) BYTE[manifestProvidersBufferSize]); + if (!manifestProvidersBuffer) + { + status = ERROR_OUTOFMEMORY; + break; + } + } + + if (ERROR_SUCCESS != status) + { + wprintf(L"TdhEnumerateProvidersForDecodingSource failed with error %lu.\n", status); + return; + } + else + { + ShowProviderInfo(reinterpret_cast(manifestProvidersBuffer.get())); + } +} +#endif + +void TdhEnumerateProvidersSample() +{ + DWORD status; + std::unique_ptr providerBuffer; + DWORD providerBufferSize = 0; + + // Available in Windows Vista or later. + // Retrieve providers registered via manifest files and via MOF class with TdhEnumerateProviders. + // Allocate the required buffer and call TdhEnumerateProviders. + // The list of providers can change between the time you retrieved the required + // buffer size and the time you enumerated the providers, so call + // TdhEnumerateProviders in a loop until the function does not return + // ERROR_INSUFFICIENT_BUFFER. + for (;;) + { + status = TdhEnumerateProviders( + reinterpret_cast(providerBuffer.get()), + &providerBufferSize); + + if (status != ERROR_INSUFFICIENT_BUFFER) + { + break; + } + + providerBuffer.reset(new(std::nothrow) BYTE[providerBufferSize]); + if (!providerBuffer) + { + status = ERROR_OUTOFMEMORY; + break; + } + + } + + if (ERROR_SUCCESS != status) + { + wprintf(L"TdhEnumerateProviders failed with error %lu.\n", status); + return; + } + else + { + ShowProviderInfo(reinterpret_cast(providerBuffer.get())); + } +} + +int wmain(void) +{ + wprintf(L"TdhEnumerateProviders output:\n"); + TdhEnumerateProvidersSample(); + +#if defined(NTDDI_WIN10_MN) && (NTDDI_VERSION >= NTDDI_WIN10_MN) + wprintf(L"TdhEnumerateProvidersForDecodingSource output:\n"); + TdhEnumerateProvidersForDecodingSourceSample(); +#endif +} diff --git a/Samples/EventTracingEnumerateProviders/EnumerateProviders.sln b/Samples/EventTracingEnumerateProviders/EnumerateProviders.sln new file mode 100644 index 00000000..a13ae24a --- /dev/null +++ b/Samples/EventTracingEnumerateProviders/EnumerateProviders.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.1062 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EnumerateProviders", "EnumerateProviders.vcxproj", "{F224F38C-DD89-4445-88E5-42312303B945}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F224F38C-DD89-4445-88E5-42312303B945}.Debug|x64.ActiveCfg = Debug|x64 + {F224F38C-DD89-4445-88E5-42312303B945}.Debug|x64.Build.0 = Debug|x64 + {F224F38C-DD89-4445-88E5-42312303B945}.Debug|x86.ActiveCfg = Debug|Win32 + {F224F38C-DD89-4445-88E5-42312303B945}.Debug|x86.Build.0 = Debug|Win32 + {F224F38C-DD89-4445-88E5-42312303B945}.Release|x64.ActiveCfg = Release|x64 + {F224F38C-DD89-4445-88E5-42312303B945}.Release|x64.Build.0 = Release|x64 + {F224F38C-DD89-4445-88E5-42312303B945}.Release|x86.ActiveCfg = Release|Win32 + {F224F38C-DD89-4445-88E5-42312303B945}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {490C78A3-D5A1-4BC1-B4A3-99C8FF32BDB2} + EndGlobalSection +EndGlobal diff --git a/Samples/EventTracingEnumerateProviders/EnumerateProviders.vcxproj b/Samples/EventTracingEnumerateProviders/EnumerateProviders.vcxproj new file mode 100644 index 00000000..3bbcc7af --- /dev/null +++ b/Samples/EventTracingEnumerateProviders/EnumerateProviders.vcxproj @@ -0,0 +1,91 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {F224F38C-DD89-4445-88E5-42312303B945} + EnumerateProviders + 10.0 + + + + Application + v142 + v143 + Unicode + + + true + + + false + true + + + + + + + + + + + + + + + + + + + + + + Level3 + true + true + + + Console + + + + + Disabled + + + + + MaxSpeed + true + true + + + true + true + + + + + + + + + \ No newline at end of file diff --git a/Samples/EventTracingEnumerateProviders/EnumerateProviders.vcxproj.filters b/Samples/EventTracingEnumerateProviders/EnumerateProviders.vcxproj.filters new file mode 100644 index 00000000..21d232fb --- /dev/null +++ b/Samples/EventTracingEnumerateProviders/EnumerateProviders.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/Samples/EventTracingEnumerateProviders/README.md b/Samples/EventTracingEnumerateProviders/README.md new file mode 100644 index 00000000..6b79b8e5 --- /dev/null +++ b/Samples/EventTracingEnumerateProviders/README.md @@ -0,0 +1,42 @@ +--- +page_type: sample +languages: +- cpp +products: +- windows-api-win32 +name: Event tracing provider enumeration +urlFragment: eventtracing-enumerateproviders +description: Enumerates providers that have registered Event Tracing for Windows (ETW) decoding information +extendedZipContent: +- path: LICENSE + target: LICENSE +--- + +Event tracing provider enumeration sample +========================================= +This sample demonstrates how to get information about the providers that have +registered ETW decoding information on the system. This sample uses the +TdhEnumerateProviders and TdhEnumerateProvidersForDecodingSource APIs. + +Prerequisites +============= +- TdhEnumerateProviders requires Windows Vista or later. +- TdhEnumerateProvidersForDecodingSource requires Windows 10 build 20348 or higher. +- This sample requires the Windows Software Development Kit. + +Sample language implementations +=============================== +C++ + +To build the sample using Visual Studio (preferred method): +================================================================ + 1. Open File Explorer and navigate to the directory. + 2. Double-click the icon for the .sln (solution) file to open the file in + Visual Studio. + 3. In the Build menu, select Build Solution. The application will be + built in the default \Debug or \Release directory. + + +To run the sample: +================== + 1. Press F5 in Visual Studio or later. diff --git a/Samples/WASAPIRendering/CmdLine.cpp b/Samples/WASAPIRendering/CmdLine.cpp new file mode 100644 index 00000000..b62ae34a --- /dev/null +++ b/Samples/WASAPIRendering/CmdLine.cpp @@ -0,0 +1,149 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +#include "pch.h" +#include "CmdLine.h" + +// +// Parse the command line and set options based on those arguments. +// +bool ParseCommandLine(int argc, wchar_t* argv[], const CommandLineSwitch Switches[], size_t SwitchCount) +{ + // + // Iterate over the command line arguments + for (int i = 1; i < argc; i += 1) + { + if (argv[i][0] == L'-' || argv[i][0] == L'/') + { + size_t switchIndex; + for (switchIndex = 0; switchIndex < SwitchCount; switchIndex += 1) + { + size_t switchNameLength = wcslen(Switches[switchIndex].SwitchName); + if (_wcsnicmp(&argv[i][1], Switches[switchIndex].SwitchName, switchNameLength) == 0 && + (argv[i][switchNameLength + 1] == L':' || argv[i][switchNameLength + 1] == '\0')) + { + wchar_t* switchValue = NULL; + + if (Switches[switchIndex].SwitchType != CommandLineSwitch::SwitchTypeNone) + { + // + // This is a switch value that expects an argument. + // + // Check to see if the last character of the argument is a ":". + // + // If it is, then the user specified an argument, so we should use the value after the ":" + // as the argument. + // + if (argv[i][switchNameLength + 1] == L':') + { + switchValue = &argv[i][switchNameLength + 2]; + } + else if (i < argc) + { + // + // If the switch value isn't optional, the next argument + // must be the value. + // + if (!Switches[switchIndex].SwitchValueOptional) + { + switchValue = argv[i + 1]; + i += 1; // Skip the argument. + } + // + // Otherwise the switch value is optional, so check the next parameter. + // + // If it's a switch, the user didn't specify a value, if it's not a switch + // the user DID specify a value. + // + else if (argv[i + 1][0] != L'-' && argv[i + 1][0] != L'/') + { + switchValue = argv[i + 1]; + i += 1; // Skip the argument. + } + } + else if (!Switches[switchIndex].SwitchValueOptional) + { + printf("Invalid command line argument parsing option %ls\n", Switches[switchIndex].SwitchName); + return false; + } + } + switch (Switches[switchIndex].SwitchType) + { + // + // SwitchTypeNone switches take a boolean parameter indiating whether or not the parameter was present. + // + case CommandLineSwitch::SwitchTypeNone: + *reinterpret_cast(Switches[switchIndex].SwitchValue) = true; + break; + // + // SwitchTypeInteger switches take an integer parameter. + // + case CommandLineSwitch::SwitchTypeInteger: + { + wchar_t* endValue; + long value = wcstoul(switchValue, &endValue, 0); + if (value == ULONG_MAX || value == 0 || (*endValue != L'\0' && !iswspace(*endValue))) + { + printf("Command line switch %ls expected an integer value, received %ls\n", Switches[switchIndex].SwitchName, switchValue); + return false; + } + *reinterpret_cast(Switches[switchIndex].SwitchValue) = value; + break; + } + // + // SwitchTypeString switches take a string parameter - allocate a buffer for the string using operator new[]. + // + case CommandLineSwitch::SwitchTypeString: + { + wchar_t** switchLocation = reinterpret_cast(Switches[switchIndex].SwitchValue); + // + // If the user didn't specify a value, set the location to NULL. + // + if (switchValue == NULL || *switchValue == '\0') + { + *switchLocation = NULL; + } + else + { + size_t switchLength = wcslen(switchValue) + 1; + *switchLocation = new (std::nothrow) wchar_t[switchLength]; + if (*switchLocation == NULL) + { + printf("Unable to allocate memory for switch %ls", Switches[switchIndex].SwitchName); + return false; + } + + HRESULT hr = StringCchCopy(*switchLocation, switchLength, switchValue); + if (FAILED(hr)) + { + printf("Unable to copy command line string %ls to buffer\n", switchValue); + return false; + } + } + break; + } + default: + break; + } + // We've processed this command line switch, we can move to the next argument. + // + break; + } + } + if (switchIndex == SwitchCount) + { + printf("unrecognized switch: %ls\n", argv[i]); + return false; + } + } + } + return true; +} diff --git a/Samples/WASAPIRendering/CmdLine.h b/Samples/WASAPIRendering/CmdLine.h new file mode 100644 index 00000000..918e05ef --- /dev/null +++ b/Samples/WASAPIRendering/CmdLine.h @@ -0,0 +1,31 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// +// Command line parsing logic +// +struct CommandLineSwitch +{ + enum CommandLineSwitchType + { + SwitchTypeNone, + SwitchTypeInteger, + SwitchTypeString, + }; + + LPCWSTR SwitchName; + LPCWSTR SwitchHelp; + CommandLineSwitchType SwitchType; + void* SwitchValue; + bool SwitchValueOptional; +}; + +bool ParseCommandLine(int argc, wchar_t* argv[], const CommandLineSwitch Switches[], size_t SwitchCount); diff --git a/Samples/WASAPIRendering/README.md b/Samples/WASAPIRendering/README.md new file mode 100644 index 00000000..607db4aa --- /dev/null +++ b/Samples/WASAPIRendering/README.md @@ -0,0 +1,61 @@ +--- +page_type: sample +languages: +- cpp +- cppwinrt +products: +- windows-api-win32 +name: Windows Audio Session (WASAPI) rendering sample +urlFragment: wasapi-rendering +description: Renders audio data using the Windows Audio Session API (WASAPI) +extendedZipContent: +- path: LICENSE + target: LICENSE +--- + +# Windows Audio Session (WASAPI) rendering sample + +This sample demonstrates rendering audio data using the Windows Audio Session API (WASAPI). + +The sample renders audio in shared mode using the event driven programming model. + +The following additional features are demonstrated: +* Using `IAudioViewManagerService` to associate the audio stream with a window. + If you do not explicitly associate the stream with a window, + the system will try to guess which window to use. + Use the `-w` command line option to associate the audio stream with the console window. + +Support for the `IAudioViewManagerService` interface requires SDK version 10.0.22470.0 or higher. + +## Sample language + +C++ with the C++/WinRT library + +## Sample structure + +The main program is in `WASAPIRendering.cpp`. +It parses the command line and uses a `CWASAPIRenderer` object +to perform the rendering. + +The `CWASAPIRenderer` is implemented in `WASAPIRenderer.cpp`. +It uses WASAPI to render a buffer containing audio data. + +The audio samples are generated by `ToneGen.h`. + +The command line parser is in `CmdLine.cpp` + +## Related samples + +More audio samples can be found in the `Samples/Win7Samples/multimedia/audio` directory. + +## To build the sample using Visual Studio + +* Load the .sln (solution) file into Visual Studio. +* In the Build menu, select Build Solution. + The application will be built in the default \Debug or \Release directory + +## To run the sample + +Go to the build output directory and type `wasapirendering.exe`. + +You can add the `-?` flag to see command line options. diff --git a/Samples/WASAPIRendering/ToneGen.h b/Samples/WASAPIRendering/ToneGen.h new file mode 100644 index 00000000..8cd0fe1b --- /dev/null +++ b/Samples/WASAPIRendering/ToneGen.h @@ -0,0 +1,72 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// +// Sine tone generator. +// +#include "pch.h" + +#define _USE_MATH_DEFINES +#include +#include + +// Convert from [-1.0...+1.0] to sample format. +template +T Convert(double Value) +{ + using limits = std::numeric_limits; + if constexpr (limits::is_integer) + { + // Scale to [-limits::max() .. +limits::max() ] + static_assert(limits::is_signed); + return static_cast(Value * (limits::max)()); + } + else + { + // Floating point types all use the range [-1.0 .. +1.0] + return static_cast(Value); + } +} + + + +// +// Generate samples which represent a sine wave that fits into the specified buffer. +// +// T: Type of data holding the sample (short, int, byte, float) +// Buffer - Buffer to hold the samples +// BufferLength - Length of the buffer. +// ChannelCount - Number of channels per audio frame. +// SamplesPerSecond - Samples/Second for the output data. +// InitialTheta - Initial theta value - start at 0, modified in this function. +// +template +void GenerateSineSamples(BYTE* Buffer, size_t BufferLength, DWORD Frequency, WORD ChannelCount, DWORD SamplesPerSecond, double* InitialTheta) +{ + double sampleIncrement = (Frequency * (M_PI * 2)) / (double)SamplesPerSecond; + T* dataBuffer = reinterpret_cast(Buffer); + double theta = (InitialTheta != NULL ? *InitialTheta : 0); + + for (size_t i = 0; i < BufferLength / sizeof(T); i += ChannelCount) + { + double sinValue = sin(theta); + for (size_t j = 0; j < ChannelCount; j++) + { + dataBuffer[i + j] = Convert(sinValue); + } + theta += sampleIncrement; + } + + if (InitialTheta != NULL) + { + *InitialTheta = theta; + } +} diff --git a/Samples/WASAPIRendering/WASAPIRenderer.cpp b/Samples/WASAPIRendering/WASAPIRenderer.cpp new file mode 100644 index 00000000..be131729 --- /dev/null +++ b/Samples/WASAPIRendering/WASAPIRenderer.cpp @@ -0,0 +1,605 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +#include "pch.h" +#include +#include +#include "WASAPIRenderer.h" + +// +// A simple WASAPI Render client. +// + +void CWASAPIRenderer::SetUp(IMMDevice* Endpoint, bool EnableStreamSwitch, ERole EndpointRole, bool EnableAudioViewManagerService) +{ + _endpoint = Endpoint; + _enableStreamSwitch = EnableStreamSwitch; + _endpointRole = EndpointRole; + _enableAudioViewManagerService = EnableAudioViewManagerService; +} + +// +// Empty destructor - everything should be released in the Shutdown() call. +// +CWASAPIRenderer::~CWASAPIRenderer(void) +{ +} + +// +// Initialize WASAPI in event driven mode. +// +HRESULT CWASAPIRenderer::InitializeAudioEngine() +{ + RETURN_IF_FAILED(_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, + _engineLatencyInMS * (long long)10000, + 0, + _mixFormat.get(), + NULL)); + + // + // Retrieve the buffer size for the audio client. + // + RETURN_IF_FAILED(_audioClient->GetBufferSize(&_bufferSize)); + + RETURN_IF_FAILED(_audioClient->SetEventHandle(_audioSamplesReadyEvent.get())); + + RETURN_IF_FAILED(_audioClient->GetService(IID_PPV_ARGS(&_renderClient))); + + if (_enableAudioViewManagerService) + { + auto const hr = [&] + { + wil::com_ptr_nothrow audioViewManagerService; + RETURN_IF_FAILED(_audioClient->GetService(IID_PPV_ARGS(&audioViewManagerService))); + // Pass the window that this audio stream is associated with. + // This is used by the system for purposes such as rendering spatial audio + // in Mixed Reality scenarios. + RETURN_IF_FAILED(audioViewManagerService->SetAudioStreamWindow(GetConsoleWindow())); + + return S_OK; + }(); + if (SUCCEEDED(hr)) + { + printf("Audio stream has been associated with the console window\n"); + } + else + { + printf("Unable to associate the audio stream with a window\n"); + } + } + + return S_OK; +} + +// +// The Event Driven renderer will be woken up every defaultDevicePeriod hundred-nano-seconds. +// Convert that time into a number of frames. +// +UINT32 CWASAPIRenderer::BufferSizePerPeriod() +{ + REFERENCE_TIME defaultDevicePeriod, minimumDevicePeriod; + if (FAILED(_audioClient->GetDevicePeriod(&defaultDevicePeriod, &minimumDevicePeriod))) + { + printf("Unable to retrieve device period\n"); + return 0; + } + double devicePeriodInSeconds = defaultDevicePeriod / (10000.0 * 1000.0); + return static_cast(_mixFormat->nSamplesPerSec * devicePeriodInSeconds + 0.5); +} + +// +// Retrieve the format we'll use to render samples. +// +// We use the Mix format since we're rendering in shared mode. +// +HRESULT CWASAPIRenderer::LoadFormat() +{ + RETURN_IF_FAILED(_audioClient->GetMixFormat(wil::out_param(_mixFormat))); + + _frameSize = _mixFormat->nBlockAlign; + RETURN_IF_FAILED(CalculateMixFormatType()); + + return S_OK; +} + +// +// Crack open the mix format and determine what kind of samples are being rendered. +// +HRESULT CWASAPIRenderer::CalculateMixFormatType() +{ + if (_mixFormat->wFormatTag == WAVE_FORMAT_PCM || + _mixFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + reinterpret_cast(_mixFormat.get())->SubFormat == KSDATAFORMAT_SUBTYPE_PCM) + { + if (_mixFormat->wBitsPerSample == 16) + { + _renderSampleType = RenderSampleType::Pcm16Bit; + } + else + { + printf("Unknown PCM integer sample type\n"); + return E_UNEXPECTED; + } + } + else if (_mixFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || + (_mixFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + reinterpret_cast(_mixFormat.get())->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) + { + _renderSampleType = RenderSampleType::Float; + } + else + { + printf("unrecognized device format.\n"); + return E_UNEXPECTED; + } + return S_OK; +} +// +// Initialize the renderer. +// +HRESULT CWASAPIRenderer::Initialize(UINT32 EngineLatency) +{ + if (EngineLatency < 30) + { + printf("Engine latency in shared mode event driven cannot be less than 30ms\n"); + return E_UNEXPECTED; + } + + // + // Create our shutdown and samples ready events- we want auto reset events that start in the not-signaled state. + // + RETURN_IF_FAILED(_shutdownEvent.create()); + + RETURN_IF_FAILED(_audioSamplesReadyEvent.create()); + + // + // Create our stream switch event- we want auto reset events that start in the not-signaled state. + // Note that we create this event even if we're not going to stream switch - that's because the event is used + // in the main loop of the renderer and thus it has to be set. + // + RETURN_IF_FAILED(_streamSwitchEvent.create()); + + // + // Now activate an IAudioClient object on our preferred endpoint and retrieve the mix format for that endpoint. + // + RETURN_IF_FAILED(_endpoint->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast(&_audioClient))); + + RETURN_IF_FAILED(CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_deviceEnumerator))); + + // + // Load the MixFormat. This may differ depending on the shared mode used + // + RETURN_IF_FAILED(LoadFormat()); + + // + // Remember our configured latency in case we'll need it for a stream switch later. + // + _engineLatencyInMS = EngineLatency; + RETURN_IF_FAILED(InitializeAudioEngine()); + + if (_enableStreamSwitch) + { + RETURN_IF_FAILED(InitializeStreamSwitch()); + } + + return S_OK; +} + +// +// Shut down the render code and free all the resources. +// +void CWASAPIRenderer::Shutdown() +{ + if (_renderThread) + { + SetEvent(_shutdownEvent.get()); + WaitForSingleObject(_renderThread.get(), INFINITE); + _renderThread.reset(); + } + + if (_enableStreamSwitch) + { + TerminateStreamSwitch(); + } +} + + +// +// Start rendering - Create the render thread and start rendering the buffer. +// +HRESULT CWASAPIRenderer::Start(std::forward_list&& RenderBufferQueue) +{ + _renderBufferQueue = std::move(RenderBufferQueue); + + // + // We want to pre-roll the first buffer's worth of data into the pipeline. That way the audio engine won't glitch on startup. + // + { + BYTE* pData; + + if (_renderBufferQueue.empty()) + { + RETURN_IF_FAILED(_renderClient->GetBuffer(_bufferSize, &pData)); + RETURN_IF_FAILED(_renderClient->ReleaseBuffer(_bufferSize, AUDCLNT_BUFFERFLAGS_SILENT)); + } + else + { + // + // Remove the buffer from the queue. + // + std::forward_list head; + head.splice_after(head.before_begin(), _renderBufferQueue, _renderBufferQueue.before_begin()); + RenderBuffer& renderBuffer = head.front(); + DWORD bufferLengthInFrames = renderBuffer._bufferLength / _frameSize; + + RETURN_IF_FAILED(_renderClient->GetBuffer(bufferLengthInFrames, &pData)); + + CopyMemory(pData, renderBuffer._buffer.get(), renderBuffer._bufferLength); + RETURN_IF_FAILED(_renderClient->ReleaseBuffer(bufferLengthInFrames, 0)); + } + } + + // + // Now create the thread which is going to drive the renderer. + // + _renderThread.reset(CreateThread(NULL, 0, WASAPIRenderThread, this, 0, NULL)); + RETURN_LAST_ERROR_IF_NULL(_renderThread); + + // + // We're ready to go, start rendering! + // + RETURN_IF_FAILED(_audioClient->Start()); + + return S_OK; +} + +// +// Stop the renderer. +// +void CWASAPIRenderer::Stop() +{ + // + // Tell the render thread to shut down, wait for the thread to complete then clean up all the stuff we + // allocated in Start(). + // + if (_shutdownEvent) + { + SetEvent(_shutdownEvent.get()); + } + + if (FAILED(_audioClient->Stop())) + { + printf("Unable to stop audio client\n"); + } + + if (_renderThread) + { + WaitForSingleObject(_renderThread.get(), INFINITE); + _renderThread.reset(); + } + + // + // Drain the buffers in the render buffer queue. + // + _renderBufferQueue.clear(); +} + + +// +// Render thread - processes samples from the audio engine +// +DWORD CWASAPIRenderer::WASAPIRenderThread(LPVOID Context) +{ + CWASAPIRenderer* renderer = static_cast(Context); + return renderer->DoRenderThread(); +} + +DWORD CWASAPIRenderer::DoRenderThread() +{ + bool stillPlaying = true; + wil::unique_couninitialize_call uninitialize(false); + + HANDLE waitArray[3] = { _shutdownEvent.get(), _streamSwitchEvent.get(), _audioSamplesReadyEvent.get() }; + + if (SUCCEEDED(CoInitializeEx(NULL, COINIT_MULTITHREADED))) + { + uninitialize.activate(); + } + else + { + printf("Unable to initialize COM in render thread\n"); + } + + while (stillPlaying) + { + DWORD waitResult = WaitForMultipleObjects(3, waitArray, FALSE, INFINITE); + switch (waitResult) + { + case WAIT_OBJECT_0 + 0: // _shutdownEvent + stillPlaying = false; // We're done, exit the loop. + break; + case WAIT_OBJECT_0 + 1: // _streamSwitchEvent + // + // We've received a stream switch request. + // + // We need to stop the renderer, tear down the _audioClient and _renderClient objects and re-create them on the new. + // endpoint if possible. If this fails, abort the thread. + // + stillPlaying = SUCCEEDED(HandleStreamSwitchEvent()); + break; + case WAIT_OBJECT_0 + 2: // _audioSamplesReadyEvent + stillPlaying = SUCCEEDED(ProduceAudioFrames()); + break; + } + } + + return 0; +} + +HRESULT CWASAPIRenderer::ProduceAudioFrames() +{ + // + // We need to provide the next buffer of samples to the audio renderer. + // + + UINT32 padding; + // + // We want to find out how much of the buffer *isn't* available (is padding). + // + RETURN_IF_FAILED(_audioClient->GetCurrentPadding(&padding)); + + // + // Calculate the number of frames available. We'll render + // that many frames or the number of frames left in the buffer, whichever is smaller. + // + UINT32 framesAvailable = _bufferSize - padding; + + // + // Stop if we have nothing more to render. + // + if (_renderBufferQueue.empty()) + { + return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF); + } + + // + // If the buffer at the head of the render buffer queue does not fit in the frames available, + // then skip this pass. We will have more room on the next pass. + // + if (_renderBufferQueue.front()._bufferLength > (framesAvailable * _frameSize)) + { + return S_OK; + } + + // + // Remove the first buffer from the head of the queue. + // + std::forward_list head; + head.splice_after(head.before_begin(), _renderBufferQueue, _renderBufferQueue.before_begin()); + RenderBuffer& renderBuffer = head.front(); + + // + // Copy data from the render buffer to the output buffer and bump our render pointer. + // + UINT32 framesToWrite = renderBuffer._bufferLength / _frameSize; + BYTE* pData; + RETURN_IF_FAILED(_renderClient->GetBuffer(framesToWrite, &pData)); + CopyMemory(pData, renderBuffer._buffer.get(), framesToWrite * _frameSize); + RETURN_IF_FAILED(_renderClient->ReleaseBuffer(framesToWrite, 0)); + + return S_OK; +} + +HRESULT CWASAPIRenderer::InitializeStreamSwitch() +{ + RETURN_IF_FAILED(_audioClient->GetService(IID_PPV_ARGS(&_audioSessionControl))); + + // + // Create the stream switch complete event- we want a manual reset event that starts in the not-signaled state. + // + RETURN_IF_FAILED(_streamSwitchCompleteEvent.create()); + + // + // Register for session and endpoint change notifications. + // + // A stream switch is initiated when we receive a session disconnect notification or we receive a default device changed notification. + // + RETURN_IF_FAILED(_audioSessionControl->RegisterAudioSessionNotification(this)); + + RETURN_IF_FAILED(_deviceEnumerator->RegisterEndpointNotificationCallback(this)); + + return S_OK; +} + +void CWASAPIRenderer::TerminateStreamSwitch() +{ + if (_audioSessionControl) + { + // Unregistration can fail if InitializeStreamSwitch failed to register. + _audioSessionControl->UnregisterAudioSessionNotification(this); + } + + if (_deviceEnumerator) + { + // Unregistration can fail if InitializeStreamSwitch failed to register. + _deviceEnumerator->UnregisterEndpointNotificationCallback(this); + } + + _streamSwitchCompleteEvent.reset(); +} + +// +// Handle the stream switch. +// +// When a stream switch happens, we want to do several things in turn: +// +// 1) Stop the current renderer. +// 2) Release any resources we have allocated (the _audioClient, _audioSessionControl (after unregistering for notifications) and +// _renderClient). +// 3) Wait until the default device has changed (or 500ms has elapsed). If we time out, we need to abort because the stream switch can't happen. +// 4) Retrieve the new default endpoint for our role. +// 5) Re-instantiate the audio client on that new endpoint. +// 6) Retrieve the mix format for the new endpoint. If the mix format doesn't match the old endpoint's mix format, we need to abort because the stream +// switch can't happen. +// 7) Re-initialize the _audioClient. +// 8) Re-register for session disconnect notifications and reset the stream switch complete event. +// +HRESULT CWASAPIRenderer::HandleStreamSwitchEvent() +{ + DWORD waitResult; + + assert(_inStreamSwitch); + _inStreamSwitch = false; + // + // Step 1. Stop rendering. + // + RETURN_IF_FAILED(_audioClient->Stop()); + + // + // Step 2. Release our resources. Note that we don't release the mix format, we need it for step 6. + // + RETURN_IF_FAILED(_audioSessionControl->UnregisterAudioSessionNotification(this)); + + _audioSessionControl.reset(); + _renderClient.reset(); + _audioClient.reset(); + _endpoint.reset(); + + // + // Step 3. Wait for the default device to change. + // + // There is a race between the session disconnect arriving and the new default device + // arriving (if applicable). Wait the shorter of 500 milliseconds or the arrival of the + // new default device, then attempt to switch to the default device. In the case of a + // format change (i.e. the default device does not change), we artificially generate a + // new default device notification so the code will not needlessly wait 500ms before + // re-opening on the new format. (However, note below in step 6 that in this SDK + // sample, we are unlikely to actually successfully absorb a format change, but a + // real audio application implementing stream switching would re-format their + // pipeline to deliver the new format). + // + waitResult = WaitForSingleObject(_streamSwitchCompleteEvent.get(), 500); + if (waitResult == WAIT_TIMEOUT) + { + printf("Stream switch timeout - aborting...\n"); + return E_UNEXPECTED; + } + + // + // Step 4. If we can't get the new endpoint, we need to abort the stream switch. If there IS a new device, + // we should be able to retrieve it. + // + RETURN_IF_FAILED(_deviceEnumerator->GetDefaultAudioEndpoint(eRender, _endpointRole, &_endpoint)); + + // + // Step 5 - Re-instantiate the audio client on the new endpoint. + // + RETURN_IF_FAILED(_endpoint->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast(&_audioClient))); + + // + // Step 6 - Retrieve the new mix format. + // + wil::unique_cotaskmem_ptr wfxNew; + RETURN_IF_FAILED(_audioClient->GetMixFormat(wil::out_param(wfxNew))); + + // + // Note that this is an intentionally naive comparison. A more sophisticated comparison would + // compare the sample rate, channel count and format and apply the appropriate conversions into the render pipeline. + // + if (memcmp(_mixFormat.get(), wfxNew.get(), sizeof(WAVEFORMATEX) + wfxNew->cbSize) != 0) + { + printf("New mix format doesn't match old mix format. Aborting.\n"); + return E_UNEXPECTED; + } + + // + // Step 7: Re-initialize the audio client. + // + RETURN_IF_FAILED(InitializeAudioEngine()); + + // + // Step 8: Re-register for session disconnect notifications. + // + RETURN_IF_FAILED(_audioClient->GetService(IID_PPV_ARGS(&_audioSessionControl))); + RETURN_IF_FAILED(_audioSessionControl->RegisterAudioSessionNotification(this)); + + // + // Reset the stream switch complete event because it's a manual reset event. + // + ResetEvent(_streamSwitchCompleteEvent.get()); + // + // And we're done. Start rendering again. + // + RETURN_IF_FAILED(_audioClient->Start()); + + return S_OK; +} + +// +// Called when an audio session is disconnected. +// +// When a session is disconnected because of a device removal or format change event, we just want +// to let the render thread know that the session's gone away +// +HRESULT CWASAPIRenderer::OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason) +{ + if (DisconnectReason == DisconnectReasonDeviceRemoval) + { + // + // The stream was disconnected because the device we're rendering to was removed. + // + // We want to reset the stream switch complete event (so we'll block when the HandleStreamSwitchEvent function + // waits until the default device changed event occurs). + // + // Note that we _don't_ set the _streamSwitchCompleteEvent - that will be set when the OnDefaultDeviceChanged event occurs. + // + _inStreamSwitch = true; + SetEvent(_streamSwitchEvent.get()); + } + if (DisconnectReason == DisconnectReasonFormatChanged) + { + // + // The stream was disconnected because the format changed on our render device. + // + // We want to flag that we're in a stream switch and then set the stream switch event (which breaks out of the renderer). We also + // want to set the _streamSwitchCompleteEvent because we're not going to see a default device changed event after this. + // + _inStreamSwitch = true; + SetEvent(_streamSwitchEvent.get()); + SetEvent(_streamSwitchCompleteEvent.get()); + } + return S_OK; +} +// +// Called when the default render device changed. We just want to set an event which lets the stream switch logic know that it's ok to +// continue with the stream switch. +// +HRESULT CWASAPIRenderer::OnDefaultDeviceChanged(EDataFlow Flow, ERole Role, LPCWSTR /*NewDefaultDeviceId*/) +{ + if (Flow == eRender && Role == _endpointRole) + { + // + // The default render device for our configured role was changed. + // + // If we're not in a stream switch already, we want to initiate a stream switch event. + // We also we want to set the stream switch complete event. That will signal the render thread that it's ok to re-initialize the + // audio renderer. + // + if (!_inStreamSwitch) + { + _inStreamSwitch = true; + SetEvent(_streamSwitchEvent.get()); + } + SetEvent(_streamSwitchCompleteEvent.get()); + } + return S_OK; +} diff --git a/Samples/WASAPIRendering/WASAPIRenderer.h b/Samples/WASAPIRendering/WASAPIRenderer.h new file mode 100644 index 00000000..59b40bd1 --- /dev/null +++ b/Samples/WASAPIRendering/WASAPIRenderer.h @@ -0,0 +1,120 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +#pragma once +#include +#include +#include +#include +#include +#include + +using namespace Microsoft::WRL; + +struct RenderBuffer +{ + UINT32 _bufferLength; + std::unique_ptr _buffer; + + RenderBuffer(UINT32 length) : + _bufferLength(length), + _buffer(new BYTE[length]) + { + } +}; + +class CWASAPIRenderer : + public RuntimeClass< RuntimeClassFlags< ClassicCom >, IMMNotificationClient, IAudioSessionEvents > +{ +public: + // Public interface to CWASAPIRenderer. + enum RenderSampleType + { + Float, + Pcm16Bit, + }; + + CWASAPIRenderer() = default; + ~CWASAPIRenderer(void); + void SetUp(IMMDevice* Endpoint, bool EnableStreamSwitch, ERole EndpointRole, bool EnableAudioViewManagerService); + HRESULT Initialize(UINT32 EngineLatency); + void Shutdown(); + HRESULT Start(std::forward_list&& RenderBufferQueue); + void Stop(); + WORD ChannelCount() { return _mixFormat->nChannels; } + UINT32 SamplesPerSecond() { return _mixFormat->nSamplesPerSec; } + UINT32 BytesPerSample() { return _mixFormat->wBitsPerSample / 8; } + RenderSampleType SampleType() { return _renderSampleType; } + UINT32 FrameSize() { return _frameSize; } + UINT32 BufferSize() { return _bufferSize; } + UINT32 BufferSizePerPeriod(); + +private: + // + // Core Audio Rendering member variables. + // + wil::com_ptr_nothrow _endpoint; + wil::com_ptr_nothrow _audioClient; + wil::com_ptr_nothrow _renderClient; + + wil::unique_handle _renderThread; + wil::unique_event_nothrow _shutdownEvent; + wil::unique_event_nothrow _audioSamplesReadyEvent; + wil::unique_cotaskmem_ptr _mixFormat; + UINT32 _frameSize = 0; + UINT32 _bufferSize = 0; + LONG _engineLatencyInMS = 0; + bool _enableAudioViewManagerService = false; + RenderSampleType _renderSampleType = RenderSampleType::Pcm16Bit; + + // + // Render buffer management. + // + std::forward_list _renderBufferQueue; + + static DWORD __stdcall WASAPIRenderThread(LPVOID Context); + DWORD DoRenderThread(); + // + // Stream switch related members and methods. + // + bool _enableStreamSwitch = false; + ERole _endpointRole = eConsole; + wil::unique_event_nothrow _streamSwitchEvent; // Set when the current session is disconnected or the default device changes. + wil::unique_event_nothrow _streamSwitchCompleteEvent; // Set when the default device changed. + wil::com_ptr_nothrow _audioSessionControl; + wil::com_ptr_nothrow _deviceEnumerator; + bool _inStreamSwitch = false; + + HRESULT InitializeStreamSwitch(); + void TerminateStreamSwitch(); + HRESULT HandleStreamSwitchEvent(); + HRESULT ProduceAudioFrames(); + + STDMETHOD(OnDisplayNameChanged) (LPCWSTR /*NewDisplayName*/, LPCGUID /*EventContext*/) { return S_OK; }; + STDMETHOD(OnIconPathChanged) (LPCWSTR /*NewIconPath*/, LPCGUID /*EventContext*/) { return S_OK; }; + STDMETHOD(OnSimpleVolumeChanged) (float /*NewSimpleVolume*/, BOOL /*NewMute*/, LPCGUID /*EventContext*/) { return S_OK; } + STDMETHOD(OnChannelVolumeChanged) (DWORD /*ChannelCount*/, float /*NewChannelVolumes*/[], DWORD /*ChangedChannel*/, LPCGUID /*EventContext*/) { return S_OK; }; + STDMETHOD(OnGroupingParamChanged) (LPCGUID /*NewGroupingParam*/, LPCGUID /*EventContext*/) { return S_OK; }; + STDMETHOD(OnStateChanged) (AudioSessionState /*NewState*/) { return S_OK; }; + STDMETHOD(OnSessionDisconnected) (AudioSessionDisconnectReason DisconnectReason); + STDMETHOD(OnDeviceStateChanged) (LPCWSTR /*DeviceId*/, DWORD /*NewState*/) { return S_OK; } + STDMETHOD(OnDeviceAdded) (LPCWSTR /*DeviceId*/) { return S_OK; }; + STDMETHOD(OnDeviceRemoved) (LPCWSTR /*DeviceId(*/) { return S_OK; }; + STDMETHOD(OnDefaultDeviceChanged) (EDataFlow Flow, ERole Role, LPCWSTR NewDefaultDeviceId); + STDMETHOD(OnPropertyValueChanged) (LPCWSTR /*DeviceId*/, const PROPERTYKEY /*Key*/) { return S_OK; }; + + // + // Utility functions. + // + HRESULT CalculateMixFormatType(); + HRESULT InitializeAudioEngine(); + HRESULT LoadFormat(); +}; diff --git a/Samples/WASAPIRendering/WASAPIRendering.cpp b/Samples/WASAPIRendering/WASAPIRendering.cpp new file mode 100644 index 00000000..54c68023 --- /dev/null +++ b/Samples/WASAPIRendering/WASAPIRendering.cpp @@ -0,0 +1,320 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* +// +// WASAPIRendering.cpp : Scaffolding associated with the WASAPI Rendering sample application. +// + +#include "pch.h" + +#include // PKEY_Device_FriendlyName +#include "WASAPIRenderer.h" + +#include "CmdLine.h" +#include "ToneGen.h" +#include +#include + + +int TargetFrequency = 440; +int TargetLatency = 30; +int TargetDurationInSec = 10; +bool ShowHelp; +bool UseConsoleDevice; +bool UseCommunicationsDevice; +bool UseMultimediaDevice; +bool DisableMMCSS; +bool EnableAudioViewManagerService; + +wchar_t* OutputEndpoint; + +CommandLineSwitch CmdLineArgs[] = +{ + { L"?", L"Print this help", CommandLineSwitch::SwitchTypeNone, &ShowHelp}, + { L"h", L"Print this help", CommandLineSwitch::SwitchTypeNone, &ShowHelp}, + { L"f", L"Sine wave frequency (Hz)", CommandLineSwitch::SwitchTypeInteger, &TargetFrequency, false}, + { L"l", L"Audio Render Latency (ms)", CommandLineSwitch::SwitchTypeInteger, &TargetLatency, false}, + { L"d", L"Sine Wave Duration (s)", CommandLineSwitch::SwitchTypeInteger, &TargetDurationInSec, false}, + { L"w", L"Enable call to AudioViewManagerService", CommandLineSwitch::SwitchTypeNone, &EnableAudioViewManagerService}, + { L"console", L"Use the default console device", CommandLineSwitch::SwitchTypeNone, &UseConsoleDevice}, + { L"communications", L"Use the default communications device", CommandLineSwitch::SwitchTypeNone, &UseCommunicationsDevice}, + { L"multimedia", L"Use the default multimedia device", CommandLineSwitch::SwitchTypeNone, &UseMultimediaDevice}, + { L"endpoint", L"Use the specified endpoint ID", CommandLineSwitch::SwitchTypeString, &OutputEndpoint, true}, +}; + +size_t CmdLineArgLength = ARRAYSIZE(CmdLineArgs); + +// +// Print help for the sample +// +void Help(LPCWSTR ProgramName) +{ + printf("Usage: %ls [-/][Switch][:][Value]\n\n", ProgramName); + printf("Where Switch is one of the following: \n"); + for (size_t i = 0; i < CmdLineArgLength; i += 1) + { + printf(" -%ls: %ls\n", CmdLineArgs[i].SwitchName, CmdLineArgs[i].SwitchHelp); + } +} + +// +// Retrieves the device friendly name for a particular device in a device collection. +// +HRESULT GetDeviceName(IMMDeviceCollection* DeviceCollection, UINT DeviceIndex, LPWSTR* _deviceName) +{ + wil::com_ptr_nothrow device; + wil::unique_cotaskmem_string deviceId; + + RETURN_IF_FAILED(DeviceCollection->Item(DeviceIndex, &device)); + + RETURN_IF_FAILED(device->GetId(&deviceId)); + + wil::com_ptr_nothrow propertyStore; + RETURN_IF_FAILED(device->OpenPropertyStore(STGM_READ, &propertyStore)); + + wil::unique_prop_variant friendlyName; + RETURN_IF_FAILED(propertyStore->GetValue(PKEY_Device_FriendlyName, &friendlyName)); + + wil::unique_cotaskmem_string deviceName; + RETURN_IF_FAILED(wil::str_printf_nothrow(deviceName, L"%ls (%ls)", friendlyName.vt != VT_LPWSTR ? L"Unknown" : friendlyName.pwszVal, deviceId.get())); + + *_deviceName = deviceName.release(); + + return S_OK; +} +// +// Based on the input switches, pick the specified device to use. +// +HRESULT PickDevice(IMMDevice** DeviceToUse, bool* IsDefaultDevice, ERole* DefaultDeviceRole) +{ + wil::com_ptr_nothrow deviceEnumerator; + wil::com_ptr_nothrow deviceCollection; + wil::com_ptr_nothrow device; + + *IsDefaultDevice = false; // Assume we're not using the default device. + + RETURN_IF_FAILED(CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator))); + + // + // First off, if none of the console switches was specified, use the console device. + // + if (!UseConsoleDevice && !UseCommunicationsDevice && !UseMultimediaDevice && OutputEndpoint == nullptr) + { + // + // The user didn't specify an output device, prompt the user for a device and use that. + // + RETURN_IF_FAILED(deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection)); + + printf("Select an output device:\n"); + printf(" 0: Default Console Device\n"); + printf(" 1: Default Communications Device\n"); + printf(" 2: Default Multimedia Device\n"); + UINT deviceCount; + RETURN_IF_FAILED(deviceCollection->GetCount(&deviceCount)); + + for (UINT i = 0; i < deviceCount; i += 1) + { + wil::unique_cotaskmem_string deviceName; + + RETURN_IF_FAILED(GetDeviceName(deviceCollection.get(), i, &deviceName)); + printf(" %d: %ls\n", i + 3, deviceName.get()); + } + wchar_t choice[10]; + _getws_s(choice); // Note: Using the safe CRT version of _getws. + + long deviceIndex; + wchar_t* endPointer; + + deviceIndex = wcstoul(choice, &endPointer, 0); + if (deviceIndex == 0 && endPointer == choice) + { + printf("unrecognized device index: %ls\n", choice); + return E_UNEXPECTED; + } + switch (deviceIndex) + { + case 0: + UseConsoleDevice = 1; + break; + case 1: + UseCommunicationsDevice = 1; + break; + case 2: + UseMultimediaDevice = 1; + break; + default: + RETURN_IF_FAILED(deviceCollection->Item(deviceIndex - 3, &device)); + break; + } + } + else if (OutputEndpoint != nullptr) + { + RETURN_IF_FAILED(deviceEnumerator->GetDevice(OutputEndpoint, &device)); + } + + if (device == nullptr) + { + ERole deviceRole = eConsole; // Assume we're using the console role. + if (UseConsoleDevice) + { + deviceRole = eConsole; + } + else if (UseCommunicationsDevice) + { + deviceRole = eCommunications; + } + else if (UseMultimediaDevice) + { + deviceRole = eMultimedia; + } + RETURN_IF_FAILED(deviceEnumerator->GetDefaultAudioEndpoint(eRender, deviceRole, &device)); + *IsDefaultDevice = true; + *DefaultDeviceRole = deviceRole; + } + + *DeviceToUse = device.detach(); + + return S_OK; +} + +int wmain(int argc, wchar_t* argv[]) +{ + printf("WASAPI Render Shared Event Driven Sample\n"); + printf("Copyright (c) Microsoft. All Rights Reserved\n"); + printf("\n"); + + if (!ParseCommandLine(argc, argv, CmdLineArgs, CmdLineArgLength)) + { + Help(argv[0]); + return -1; + } + // + // Now that we've parsed our command line, do some semantic checks. + // + + // + // First off, show the help for the app if the user asked for it. + // + if (ShowHelp) + { + Help(argv[0]); + return 0; + } + + // + // The user can only specify one of -console, -communications or -multimedia or a specific endpoint. + // + if (((UseConsoleDevice != 0) + (UseCommunicationsDevice != 0) + (UseMultimediaDevice != 0) + (OutputEndpoint != nullptr)) > 1) + { + printf("Can only specify one of -Console, -Communications, -Multimedia, or a specific endpoint.\n"); + return -1; + } + + + // + // A GUI application should use COINIT_APARTMENTTHREADED instead of COINIT_MULTITHREADED. + // + if (FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED))) + { + printf("Unable to initialize COM\n"); + return -1; + } + wil::unique_couninitialize_call comUninitialize; + + // + // Now that we've parsed our command line, pick the device to render. + // + wil::com_ptr_nothrow device; + bool isDefaultDevice; + ERole role; + if (PickDevice(&device, &isDefaultDevice, &role) != S_OK) + { + return -1; + } + + printf("Render a %d hz Sine wave for %d seconds\n", TargetFrequency, TargetDurationInSec); + + // + // Instantiate a renderer and play a sound for TargetDuration seconds + // + // Configure the renderer to enable stream switching on the specified role if the user specified one of the default devices. + // + CWASAPIRenderer renderer; + renderer.SetUp(device.get(), isDefaultDevice, role, EnableAudioViewManagerService); + + if (renderer.Initialize(TargetLatency) == S_OK) + { + // + // We've initialized the renderer. Once we've done that, we know some information about the + // mix format and we can allocate the buffer that we're going to render. + // + // The buffer is going to contain "TargetDuration" seconds worth of PCM data. That means + // we're going to have TargetDuration*samples/second frames multiplied by the frame size. + // + UINT32 renderBufferSizeInBytes = (renderer.BufferSizePerPeriod() * renderer.FrameSize()); + size_t renderDataLength = (renderer.SamplesPerSecond() * TargetDurationInSec * renderer.FrameSize()) + (renderBufferSizeInBytes - 1); + size_t renderBufferCount = renderDataLength / (renderBufferSizeInBytes); + + // + // Build the render buffer queue. + // + std::forward_list renderQueue; + // Keep an iterator to the tail of the list so we can append elements. + auto renderQueueTail = renderQueue.before_begin(); + + double theta = 0; + + for (size_t i = 0; i < renderBufferCount; i += 1) + { + try + { + // Append another buffer to the queue. + renderQueueTail = renderQueue.emplace_after(renderQueueTail, renderBufferSizeInBytes); + } + catch (std::bad_alloc const&) + { + printf("Unable to allocate render buffer\n"); + return -1; + } + + RenderBuffer& renderBuffer = *renderQueueTail; + + // + // Generate tone data in the buffer. + // + switch (renderer.SampleType()) + { + case CWASAPIRenderer::RenderSampleType::Float: + GenerateSineSamples(renderBuffer._buffer.get(), renderBuffer._bufferLength, TargetFrequency, + renderer.ChannelCount(), renderer.SamplesPerSecond(), &theta); + break; + case CWASAPIRenderer::RenderSampleType::Pcm16Bit: + GenerateSineSamples(renderBuffer._buffer.get(), renderBuffer._bufferLength, TargetFrequency, + renderer.ChannelCount(), renderer.SamplesPerSecond(), &theta); + break; + } + } + + if (SUCCEEDED(renderer.Start(std::move(renderQueue)))) + { + do + { + printf("."); + Sleep(1000); + } while (--TargetDurationInSec); + printf("\n"); + renderer.Stop(); + } + } + renderer.Shutdown(); + + return 0; +} + diff --git a/Samples/WASAPIRendering/WASAPIRendering.sln b/Samples/WASAPIRendering/WASAPIRendering.sln new file mode 100644 index 00000000..b0e5100a --- /dev/null +++ b/Samples/WASAPIRendering/WASAPIRendering.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31529.145 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WASAPIRendering", "WASAPIRendering.vcxproj", "{ABECE033-9EAC-4443-A4D8-ABBBA3DF42F3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ABECE033-9EAC-4443-A4D8-ABBBA3DF42F3}.Debug|x64.ActiveCfg = Debug|x64 + {ABECE033-9EAC-4443-A4D8-ABBBA3DF42F3}.Debug|x64.Build.0 = Debug|x64 + {ABECE033-9EAC-4443-A4D8-ABBBA3DF42F3}.Debug|x86.ActiveCfg = Debug|Win32 + {ABECE033-9EAC-4443-A4D8-ABBBA3DF42F3}.Debug|x86.Build.0 = Debug|Win32 + {ABECE033-9EAC-4443-A4D8-ABBBA3DF42F3}.Release|x64.ActiveCfg = Release|x64 + {ABECE033-9EAC-4443-A4D8-ABBBA3DF42F3}.Release|x64.Build.0 = Release|x64 + {ABECE033-9EAC-4443-A4D8-ABBBA3DF42F3}.Release|x86.ActiveCfg = Release|Win32 + {ABECE033-9EAC-4443-A4D8-ABBBA3DF42F3}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DB30EC11-853A-4934-B2B3-B2B1B4E99AF1} + EndGlobalSection +EndGlobal diff --git a/Samples/WASAPIRendering/WASAPIRendering.vcxproj b/Samples/WASAPIRendering/WASAPIRendering.vcxproj new file mode 100644 index 00000000..d1b12906 --- /dev/null +++ b/Samples/WASAPIRendering/WASAPIRendering.vcxproj @@ -0,0 +1,172 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {abece033-9eac-4443-a4d8-abbba3df42f3} + WASAPIRendering + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + C:\Users\lujime\Desktop\wil-master\wil-master\include;$(IncludePath) + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/Samples/WASAPIRendering/WASAPIRendering.vcxproj.filters b/Samples/WASAPIRendering/WASAPIRendering.vcxproj.filters new file mode 100644 index 00000000..bdfa1c01 --- /dev/null +++ b/Samples/WASAPIRendering/WASAPIRendering.vcxproj.filters @@ -0,0 +1,51 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + + + + \ No newline at end of file diff --git a/Samples/WASAPIRendering/packages.config b/Samples/WASAPIRendering/packages.config new file mode 100644 index 00000000..31c04bdd --- /dev/null +++ b/Samples/WASAPIRendering/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Samples/WASAPIRendering/pch.cpp b/Samples/WASAPIRendering/pch.cpp new file mode 100644 index 00000000..ade82175 --- /dev/null +++ b/Samples/WASAPIRendering/pch.cpp @@ -0,0 +1,5 @@ +// +// Include the standard header and generate the precompiled header. +// + +#include "pch.h" diff --git a/Samples/WASAPIRendering/pch.h b/Samples/WASAPIRendering/pch.h new file mode 100644 index 00000000..15ab4f12 --- /dev/null +++ b/Samples/WASAPIRendering/pch.h @@ -0,0 +1,24 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +#pragma once + +#include +#include +#include + +#if !defined(NTDDI_WIN10_NI) || (NTDDI_VERSION < NTDDI_WIN10_NI) +#error This sample uses IAudioViewManagerService which requires SDK version 10.0.22470.0 or higher. +#endif + +#include +#include +#include diff --git a/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/Fakemenu.sln b/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/Fakemenu.sln index 39c121cd..7fd0e053 100644 --- a/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/Fakemenu.sln +++ b/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/Fakemenu.sln @@ -1,7 +1,9 @@  -Microsoft Visual Studio Solution File, Format Version 10.00 -# Visual Studio 2008 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Fakemenu", "Fakemenu.vcproj", "{4C77C0AE-32D6-4C7C-848B-B239A5750094}" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32407.337 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Fakemenu", "Fakemenu.vcxproj", "{4C77C0AE-32D6-4C7C-848B-B239A5750094}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -23,4 +25,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {02CAB563-2A22-4D22-A9AE-C75A6E495C1A} + EndGlobalSection EndGlobal diff --git a/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/Fakemenu.vcproj b/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/Fakemenu.vcproj deleted file mode 100644 index e1a75f20..00000000 --- a/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/Fakemenu.vcproj +++ /dev/null @@ -1,324 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/Fakemenu.vcxproj b/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/Fakemenu.vcxproj new file mode 100644 index 00000000..5dfb32e0 --- /dev/null +++ b/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/Fakemenu.vcxproj @@ -0,0 +1,84 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {4C77C0AE-32D6-4C7C-848B-B239A5750094} + Win32Proj + + + + Application + Unicode + v140 + v141 + v142 + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + + true + + + + + Level4 + true + + + true + Windows + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + EditAndContinue + + + + + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDLL + ProgramDatabase + + + true + + + + + + + + + \ No newline at end of file diff --git a/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/README.md b/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/README.md new file mode 100644 index 00000000..88b6b39b --- /dev/null +++ b/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/README.md @@ -0,0 +1,46 @@ +--- +page_type: sample +languages: +- cpp +products: +- windows-api-win32 +name: Menu theming sample application +urlFragment: fakemenu +description: Shows a custom window that looks and acts like a context menu +extendedZipContent: +- path: LICENSE + target: LICENSE +--- + + +Menu theming sample application +=============================== + +This sample demonstrates how to create a window that looks and acts like a context menu. + +- Captures mouse and keyboard input without activation. +- Behaves the same way as a system context menu. +- Renders content with theme parts as applicable. + +Operating system requirements +----------------------------- + +Windows Vista, will take advantage of newer features if available. + +Build the sample +---------------- + +1. Start Visual Studio and select **File** \> **Open** \> **Project/Solution**. + +2. Go to the directory named for the sample, and double-click the Microsoft Visual Studio Solution (.sln) file. + +3. Use **Build** \> **Build Solution** to build the sample. + +Run the sample +-------------- + +From Visual Studio, use **Debug** \> **Start Debugging** or **Start Without Debugging**. + +Right-click in the window to display the simulated context menu. + +Try navigating the menu with both mouse and keyboard. diff --git a/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/fakemenu.cpp b/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/fakemenu.cpp index 56eb95b9..1a2138d4 100644 --- a/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/fakemenu.cpp +++ b/Samples/Win7Samples/winui/shell/legacysamples/fakemenu/fakemenu.cpp @@ -7,6 +7,12 @@ // #include +#include +#include +#include + +#pragma comment(lib, "Uxtheme.lib") +#pragma comment(lib, "dwmapi.lib") #pragma comment(linker, "\"/manifestdependency:type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") @@ -39,9 +45,11 @@ // - The fake-menu appears on the correct monitor (for // multiple-monitor systems). // +// - The fake-menu switches to keyboard focus highlighting +// once keyboard menu navigation is employed. HINSTANCE g_hInstance = NULL; -HBRUSH g_hbrColor = NULL; // The selected color +COLORREF g_clrBackground; // The selected color // This is the array of predefined colors we put into the color picker. const COLORREF c_rgclrPredef[] = @@ -64,6 +72,26 @@ const COLORREF c_rgclrPredef[] = RGB(0xFF, 0xFF, 0xFF), // F = white }; +// FillRectClr +// +// Helper function to fill a rectangle with a solid color. +// +void FillRectClr(HDC hdc, LPCRECT prc, COLORREF clr) +{ + SetDCBrushColor(hdc, clr); + FillRect(hdc, prc, (HBRUSH)GetStockObject(DC_BRUSH)); +} + +bool IsHighContrast() +{ + HIGHCONTRAST hc = { 0 }; + hc.cbSize = sizeof(hc); + hc.dwFlags = 0; + + SystemParametersInfo(SPI_GETHIGHCONTRAST, sizeof(HIGHCONTRAST), &hc, 0); + return hc.dwFlags & HCF_HIGHCONTRASTON; +} + // // COLORPICKSTATE // @@ -81,8 +109,22 @@ typedef struct COLORPICKSTATE int iSel; // Which color is selected? int iResult; // Which color should be returned? HWND hwndOwner; // Our owner window - -} COLORPICKSTATE, *PCOLORPICKSTATE; + BOOL fKeyboardUsed; // Has the keyboard been used? + HTHEME hTheme; // The active theme, if any + int iPartKeyboardFocus; // MENU_POPUPITEMKBFOCUS if supported, else MENU_POPUPITEM + int iPartNonFocus; // MENU_POPUPITEM_FOCUSABLE if supported, else MENU_POPUPITEM + int iStateFocus; // MPIF_HOT if using the FOCUSABLE parts, else MPI_HOT + MARGINS marginsItem; // Margins of a popup item +} COLORPICKSTATE; + +void ColorPickState_Initialize(COLORPICKSTATE* pcps, HWND hwndOwner) +{ + pcps->fDone = FALSE; // Not done yet + pcps->iSel = -1; // No initial selection + pcps->iResult = -1; // No result + pcps->hwndOwner = hwndOwner; // Owner window + pcps->fKeyboardUsed = FALSE; // No keyboard usage yet +} #define CYCOLOR 16 // Height of a single color pick #define CXFAKEMENU 100 // Width of our fake menu @@ -101,23 +143,98 @@ void ColorPick_GetColorRect(RECT *prc, int iColor) prc->bottom = prc->top + CYCOLOR; } +// ColorPick_UpdateVisuals // +// Update the theme and nonclient rendering to match current user preference. +// +void ColorPick_UpdateVisuals(COLORPICKSTATE* pcps, HWND hwnd) +{ + if (pcps->hTheme) + { + CloseThemeData(pcps->hTheme); + pcps->hTheme = NULL; + } + + if (IsThemeActive() && !IsHighContrast()) + { + pcps->hTheme = OpenThemeData(hwnd, VSCLASS_MENU); + } + + if (pcps->hTheme) + { +#if defined(NTDDI_WIN10_CO) && (NTDDI_VERSION >= NTDDI_WIN10_CO) + DWM_WINDOW_CORNER_PREFERENCE preference = DWMWCP_ROUNDSMALL; + DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &preference, sizeof(preference)); + + COLORREF color; + if (SUCCEEDED(GetThemeColor(pcps->hTheme, MENU_POPUPBORDERS, 0, TMT_FILLCOLORHINT, &color))) + { + DwmSetWindowAttribute(hwnd, DWMWA_BORDER_COLOR, &color, sizeof(color)); + } +#endif + } + else + { +#if defined(NTDDI_WIN10_CO) && (NTDDI_VERSION >= NTDDI_WIN10_CO) + DWM_WINDOW_CORNER_PREFERENCE preference = DWMWCP_DEFAULT; + DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &preference, sizeof(preference)); + + COLORREF color = DWMWA_COLOR_DEFAULT; + DwmSetWindowAttribute(hwnd, DWMWA_BORDER_COLOR, &color, sizeof(color)); +#endif + } + +#if defined(NTDDI_WIN10_NI) && (NTDDI_VERSION >= NTDDI_WIN10_NI) + // Use the MENU_POPUPITEMKBFOCUS and MENU_POPUPITEM_FOCUSABLE parts if available. + if (pcps->hTheme && + IsThemePartDefined(pcps->hTheme, MENU_POPUPITEM_FOCUSABLE, MPIF_HOT)) + { + pcps->iPartKeyboardFocus = MENU_POPUPITEMKBFOCUS; + pcps->iPartNonFocus = MENU_POPUPITEM_FOCUSABLE; + pcps->iStateFocus = MPIF_HOT; + } + else +#endif + { + pcps->iPartKeyboardFocus = MENU_POPUPITEM; + pcps->iPartNonFocus = MENU_POPUPITEM; + pcps->iStateFocus = MPI_HOT; + } + if (pcps->hTheme && + SUCCEEDED(GetThemeMargins(pcps->hTheme, NULL, MENU_POPUPITEM, 0, TMT_CONTENTMARGINS, NULL, &pcps->marginsItem))) + { + // Successfully obtained item content margins. + } + else + { + // Use fallback values for item content margins. + pcps->marginsItem.cxLeftWidth = pcps->marginsItem.cxRightWidth = GetSystemMetrics(SM_CXEDGE); + pcps->marginsItem.cyTopHeight = pcps->marginsItem.cyBottomHeight = GetSystemMetrics(SM_CYEDGE); + } + + // Force everything to repaint with the new visuals. + InvalidateRect(hwnd, NULL, TRUE); +} + // ColorPick_OnCreate // // Stash away our state. // LRESULT ColorPick_OnCreate(HWND hwnd, LPCREATESTRUCT pcs) { - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LPARAM)(pcs->lpCreateParams)); + auto pcps = reinterpret_cast(pcs->lpCreateParams); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LPARAM)pcps); + ColorPick_UpdateVisuals(pcps, hwnd); + return 0; } // // ColorPick_OnPaint // -// Draw the color bars, and put a border around the selected color. +// Draw the background, color bars, and appropriate highlighting for the selected color. // -void ColorPick_OnPaint(PCOLORPICKSTATE pcps, HWND hwnd) +void ColorPick_OnPaint(COLORPICKSTATE* pcps, HWND hwnd) { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); @@ -126,9 +243,20 @@ void ColorPick_OnPaint(PCOLORPICKSTATE pcps, HWND hwnd) RECT rcClient; GetClientRect(hwnd, &rcClient); + // Let the theme chooses the background fill color. + if (pcps->hTheme) + { + COLORREF color; + if (SUCCEEDED(GetThemeColor(pcps->hTheme, MENU_POPUPBACKGROUND, 0, TMT_FILLCOLORHINT, &color))) + { + FillRectClr(hdc, &ps.rcPaint, color); + } + } + // For each of our predefined colors, draw it in a little // rectangular region, leaving some border so the user can // see if the item is highlighted or not. + for (int iColor = 0; iColor < ARRAYSIZE(c_rgclrPredef); iColor++) { // Build the "menu" item rect. @@ -138,17 +266,43 @@ void ColorPick_OnPaint(PCOLORPICKSTATE pcps, HWND hwnd) // If the item is highlighted, then draw a highlighted background. if (iColor == pcps->iSel) { - FillRect(hdc, &rc, GetSysColorBrush(COLOR_HIGHLIGHT)); + // Draw focus effect. + if (pcps->hTheme) + { + // If keyboard navigation active, then use the keyboard focus visuals. + // Otherwise, use the traditional visuals. + DrawThemeBackground(pcps->hTheme, hdc, + pcps->fKeyboardUsed ? pcps->iPartKeyboardFocus : MENU_POPUPITEM, + pcps->fKeyboardUsed ? pcps->iStateFocus : MPI_HOT, &rc, nullptr); + } + else + { + FillRect(hdc, &rc, GetSysColorBrush(COLOR_HIGHLIGHT)); + } + } + else + { + // Draw non-focus effect. + if (pcps->hTheme) + { + DrawThemeBackground(pcps->hTheme, hdc, + pcps->fKeyboardUsed ? pcps->iPartNonFocus : MENU_POPUPITEM, + MPI_NORMAL, &rc, nullptr); + } + else + { + // If not themed, then our background brush already filled for us. + } } - // Now shrink the rectangle by an edge and fill the rest with the + // Now shrink the rectangle by the margins and fill the rest with the // color of the item itself. - InflateRect(&rc, -GetSystemMetrics(SM_CXEDGE), - -GetSystemMetrics(SM_CYEDGE)); + rc.left += pcps->marginsItem.cxLeftWidth; + rc.right -= pcps->marginsItem.cxRightWidth; + rc.top += pcps->marginsItem.cyTopHeight; + rc.bottom -= pcps->marginsItem.cyBottomHeight; - HBRUSH hbr = CreateSolidBrush(c_rgclrPredef[iColor]); - FillRect(hdc, &rc, hbr); - DeleteObject(hbr); + FillRectClr(hdc, &rc, c_rgclrPredef[iColor]); } EndPaint(hwnd, &ps); @@ -160,7 +314,7 @@ void ColorPick_OnPaint(PCOLORPICKSTATE pcps, HWND hwnd) // // Change the selection to the specified item. // -void ColorPick_ChangeSel(PCOLORPICKSTATE pcps, HWND hwnd, int iSel) +void ColorPick_ChangeSel(COLORPICKSTATE* pcps, HWND hwnd, int iSel) { // If the selection changed, then repaint the items that need repainting. if (pcps->iSel != iSel) @@ -187,7 +341,7 @@ void ColorPick_ChangeSel(PCOLORPICKSTATE pcps, HWND hwnd, int iSel) // // Track the mouse to see if it is over any of our colors. // -void ColorPick_OnMouseMove(PCOLORPICKSTATE pcps, HWND hwnd, int x, int y) +void ColorPick_OnMouseMove(COLORPICKSTATE* pcps, HWND hwnd, int x, int y) { int iSel; @@ -209,7 +363,7 @@ void ColorPick_OnMouseMove(PCOLORPICKSTATE pcps, HWND hwnd, int x, int y) // // When the button comes up, we are done. // -void ColorPick_OnLButtonUp(PCOLORPICKSTATE pcps, HWND hwnd, int x, int y) +void ColorPick_OnLButtonUp(COLORPICKSTATE* pcps, HWND hwnd, int x, int y) { // First track to the final location, in case the user moves the mouse // REALLY FAST and immediately lets go. @@ -229,40 +383,42 @@ void ColorPick_OnLButtonUp(PCOLORPICKSTATE pcps, HWND hwnd, int x, int y) // If the Enter key is pressed, then accept the current selection. // If an arrow key is pressed, the move the selection. // -void ColorPick_OnKeyDown(PCOLORPICKSTATE pcps, HWND hwnd, WPARAM vk) +void ColorPick_OnKeyDown(COLORPICKSTATE* pcps, HWND hwnd, WPARAM vk) { + pcps->fKeyboardUsed = TRUE; + switch (vk) { - case VK_ESCAPE: - pcps->fDone = TRUE; // Abandoned - break; + case VK_ESCAPE: + pcps->fDone = TRUE; // Abandoned + break; - case VK_RETURN: - pcps->iResult = pcps->iSel; // Accept current selection - pcps->fDone = TRUE; - break; + case VK_RETURN: + pcps->iResult = pcps->iSel; // Accept current selection + pcps->fDone = TRUE; + break; - case VK_UP: - if (pcps->iSel > 0) // Decrement selection - { - ColorPick_ChangeSel(pcps, hwnd, pcps->iSel - 1); - } - else - { - ColorPick_ChangeSel(pcps, hwnd, ARRAYSIZE(c_rgclrPredef) - 1); - } - break; + case VK_UP: + if (pcps->iSel > 0) // Decrement selection + { + ColorPick_ChangeSel(pcps, hwnd, pcps->iSel - 1); + } + else + { + ColorPick_ChangeSel(pcps, hwnd, ARRAYSIZE(c_rgclrPredef) - 1); + } + break; - case VK_DOWN: // Increment selection - if (pcps->iSel + 1 < ARRAYSIZE(c_rgclrPredef)) - { - ColorPick_ChangeSel(pcps, hwnd, pcps->iSel + 1); - } - else - { - ColorPick_ChangeSel(pcps, hwnd, 0); - } - break; + case VK_DOWN: // Increment selection + if (pcps->iSel + 1 < ARRAYSIZE(c_rgclrPredef)) + { + ColorPick_ChangeSel(pcps, hwnd, pcps->iSel + 1); + } + else + { + ColorPick_ChangeSel(pcps, hwnd, 0); + } + break; } } @@ -273,37 +429,41 @@ void ColorPick_OnKeyDown(PCOLORPICKSTATE pcps, HWND hwnd, WPARAM vk) // LRESULT CALLBACK ColorPick_WndProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) { - PCOLORPICKSTATE pcps = (PCOLORPICKSTATE)GetWindowLongPtr(hwnd, GWLP_USERDATA); + COLORPICKSTATE* pcps = (COLORPICKSTATE*)GetWindowLongPtr(hwnd, GWLP_USERDATA); switch (uiMsg) { - case WM_CREATE: - return ColorPick_OnCreate(hwnd, (LPCREATESTRUCT)lParam); + case WM_CREATE: + return ColorPick_OnCreate(hwnd, (LPCREATESTRUCT)lParam); - case WM_MOUSEMOVE: - ColorPick_OnMouseMove(pcps, hwnd, (short)LOWORD(lParam), - (short)HIWORD(lParam)); - break; + case WM_MOUSEMOVE: + ColorPick_OnMouseMove(pcps, hwnd, (short)LOWORD(lParam), + (short)HIWORD(lParam)); + break; - case WM_LBUTTONUP: - ColorPick_OnLButtonUp(pcps, hwnd, (short)LOWORD(lParam), - (short)HIWORD(lParam)); - break; + case WM_LBUTTONUP: + ColorPick_OnLButtonUp(pcps, hwnd, (short)LOWORD(lParam), + (short)HIWORD(lParam)); + break; - case WM_SYSKEYDOWN: - case WM_KEYDOWN: - ColorPick_OnKeyDown(pcps, hwnd, wParam); - break; + case WM_SYSKEYDOWN: + case WM_KEYDOWN: + ColorPick_OnKeyDown(pcps, hwnd, wParam); + break; // Do not activate when somebody clicks the window. - case WM_MOUSEACTIVATE: - return MA_NOACTIVATE; + case WM_MOUSEACTIVATE: + return MA_NOACTIVATE; - case WM_PAINT: - ColorPick_OnPaint(pcps, hwnd); - return 0; - } + case WM_PAINT: + ColorPick_OnPaint(pcps, hwnd); + return 0; + case WM_THEMECHANGED: + case WM_SETTINGCHANGE: + ColorPick_UpdateVisuals(pcps, hwnd); + break; + } return DefWindowProc(hwnd, uiMsg, wParam, lParam); } @@ -387,21 +547,18 @@ int ColorPick_Popup(HWND hwndOwner, int x, int y) } COLORPICKSTATE cps; - cps.fDone = FALSE; // Not done yet - cps.iSel = -1; // No initial selection - cps.iResult = -1; // No result - cps.hwndOwner = hwndOwner; // Owner window + ColorPickState_Initialize(&cps, hwndOwner); // Set up the style and extended style we want to use. DWORD dwStyle = WS_POPUP | WS_BORDER; DWORD dwExStyle = WS_EX_TOOLWINDOW | // So it doesn't show up in taskbar - WS_EX_DLGMODALFRAME | // Get the edges right - WS_EX_WINDOWEDGE | - WS_EX_TOPMOST; // So it isn't obscured + WS_EX_DLGMODALFRAME | // Get the edges right + WS_EX_WINDOWEDGE | + WS_EX_TOPMOST; // So it isn't obscured - // We want a client area of size (CXFAKEMENU, ARRAYSIZE(c_rgclrPredef) * CYCOLOR), - // so use AdjustWindowRectEx to figure out what window rect will give us a - // client rect of that size. +// We want a client area of size (CXFAKEMENU, ARRAYSIZE(c_rgclrPredef) * CYCOLOR), +// so use AdjustWindowRectEx to figure out what window rect will give us a +// client rect of that size. RECT rc; rc.left = 0; rc.top = 0; @@ -469,49 +626,49 @@ int ColorPick_Popup(HWND hwndOwner, int x, int y) // These mouse messages arrive in client coordinates, so in // addition to stealing the message, we also need to convert the // coordinates. - case WM_MOUSEMOVE: - case WM_LBUTTONDOWN: - case WM_LBUTTONUP: - case WM_LBUTTONDBLCLK: - case WM_RBUTTONDOWN: - case WM_RBUTTONUP: - case WM_RBUTTONDBLCLK: - case WM_MBUTTONDOWN: - case WM_MBUTTONUP: - case WM_MBUTTONDBLCLK: - pt.x = (short)LOWORD(msg.lParam); - pt.y = (short)HIWORD(msg.lParam); - MapWindowPoints(msg.hwnd, hwndPopup, &pt, 1); - msg.lParam = MAKELPARAM(pt.x, pt.y); - msg.hwnd = hwndPopup; - break; + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_RBUTTONDBLCLK: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MBUTTONDBLCLK: + pt.x = (short)LOWORD(msg.lParam); + pt.y = (short)HIWORD(msg.lParam); + MapWindowPoints(msg.hwnd, hwndPopup, &pt, 1); + msg.lParam = MAKELPARAM(pt.x, pt.y); + msg.hwnd = hwndPopup; + break; // These mouse messages arrive in screen coordinates, so we just // need to steal the message. - case WM_NCMOUSEMOVE: - case WM_NCLBUTTONDOWN: - case WM_NCLBUTTONUP: - case WM_NCLBUTTONDBLCLK: - case WM_NCRBUTTONDOWN: - case WM_NCRBUTTONUP: - case WM_NCRBUTTONDBLCLK: - case WM_NCMBUTTONDOWN: - case WM_NCMBUTTONUP: - case WM_NCMBUTTONDBLCLK: - msg.hwnd = hwndPopup; - break; + case WM_NCMOUSEMOVE: + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONUP: + case WM_NCLBUTTONDBLCLK: + case WM_NCRBUTTONDOWN: + case WM_NCRBUTTONUP: + case WM_NCRBUTTONDBLCLK: + case WM_NCMBUTTONDOWN: + case WM_NCMBUTTONUP: + case WM_NCMBUTTONDBLCLK: + msg.hwnd = hwndPopup; + break; // We need to steal all keyboard messages, too. - case WM_KEYDOWN: - case WM_KEYUP: - case WM_CHAR: - case WM_DEADCHAR: - case WM_SYSKEYDOWN: - case WM_SYSKEYUP: - case WM_SYSCHAR: - case WM_SYSDEADCHAR: - msg.hwnd = hwndPopup; - break; + case WM_KEYDOWN: + case WM_KEYUP: + case WM_CHAR: + case WM_DEADCHAR: + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + case WM_SYSCHAR: + case WM_SYSDEADCHAR: + msg.hwnd = hwndPopup; + break; } TranslateMessage(&msg); @@ -538,6 +695,12 @@ int ColorPick_Popup(HWND hwndOwner, int x, int y) DestroyWindow(hwndPopup); + // Clean up any theme data. + if (cps.hTheme) + { + CloseThemeData(cps.hTheme); + } + // If we got a WM_QUIT message, then re-post it so the caller's message // loop will see it. if (msg.message == WM_QUIT) @@ -557,7 +720,7 @@ void FakeMenuDemo_OnEraseBkgnd(HWND hwnd, HDC hdc) { RECT rc; GetClientRect(hwnd, &rc); - FillRect(hdc, &rc, g_hbrColor); + FillRectClr(hdc, &rc, g_clrBackground); } // @@ -585,8 +748,7 @@ void FakeMenuDemo_OnContextMenu(HWND hwnd, int x, int y) // If the user picked a color, then change to that color. if (iColor >= 0) { - DeleteObject(g_hbrColor); - g_hbrColor = CreateSolidBrush(c_rgclrPredef[iColor]); + g_clrBackground = c_rgclrPredef[iColor]; InvalidateRect(hwnd, NULL, TRUE); } } @@ -600,18 +762,18 @@ LRESULT CALLBACK FakeMenuDemo_WndProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPAR { switch (uiMsg) { - case WM_ERASEBKGND: - FakeMenuDemo_OnEraseBkgnd(hwnd, (HDC)wParam); - return TRUE; - - case WM_CONTEXTMENU: - FakeMenuDemo_OnContextMenu(hwnd, (short)LOWORD(lParam), - (short)HIWORD(lParam)); - return 0; - - case WM_DESTROY: - PostQuitMessage(0); - break; + case WM_ERASEBKGND: + FakeMenuDemo_OnEraseBkgnd(hwnd, (HDC)wParam); + return TRUE; + + case WM_CONTEXTMENU: + FakeMenuDemo_OnContextMenu(hwnd, (short)LOWORD(lParam), + (short)HIWORD(lParam)); + return 0; + + case WM_DESTROY: + PostQuitMessage(0); + break; } return DefWindowProc(hwnd, uiMsg, wParam, lParam); @@ -639,7 +801,7 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE /* hInstPrev */, LPWSTR /* ps wc.lpszClassName = L"FakeMenuDemo"; RegisterClassEx(&wc); - g_hbrColor = CreateSolidBrush(RGB(0xFF, 0xFF, 0xFF)); + g_clrBackground = RGB(0xFF, 0xFF, 0xFF); HWND hwnd = CreateWindow( L"FakeMenuDemo",