From bd31b445b63854f8b2a2c6073cbc0652edfd5091 Mon Sep 17 00:00:00 2001 From: Raymond Chen Date: Wed, 4 Oct 2023 17:00:15 -0700 Subject: [PATCH] Windows 11 Version 22H2 - October 2023 Samples Update * WebAuthenticationBroker: Remove Google support (issue #1376) * New: MobileHotspot, LampArray --- README.md | 18 +- Samples/LampArray/README.md | 85 +++ Samples/LampArray/cppwinrt/LampArray.sln | 43 ++ Samples/LampArray/cppwinrt/LampArray.vcxproj | 193 ++++++ .../cppwinrt/LampArray.vcxproj.filters | 70 ++ .../LampArray/cppwinrt/Package.appxmanifest | 49 ++ Samples/LampArray/cppwinrt/Project.idl | 25 + .../cppwinrt/SampleConfiguration.cpp | 60 ++ .../LampArray/cppwinrt/SampleConfiguration.h | 33 + .../LampArray/cppwinrt/Scenario1_Basics.cpp | 221 +++++++ Samples/LampArray/cppwinrt/Scenario1_Basics.h | 61 ++ .../LampArray/cppwinrt/Scenario2_Effects.cpp | 592 +++++++++++++++++ .../LampArray/cppwinrt/Scenario2_Effects.h | 154 +++++ Samples/LampArray/cppwinrt/packages.config | 5 + Samples/LampArray/cppwinrt/pch.cpp | 6 + Samples/LampArray/cppwinrt/pch.h | 36 ++ Samples/LampArray/cs/LampArray.csproj | 186 ++++++ Samples/LampArray/cs/LampArray.sln | 43 ++ Samples/LampArray/cs/Package.appxmanifest | 60 ++ Samples/LampArray/cs/SampleConfiguration.cs | 49 ++ Samples/LampArray/cs/Scenario1_Basics.xaml.cs | 216 +++++++ .../LampArray/cs/Scenario2_Effects.xaml.cs | 606 ++++++++++++++++++ .../LampArray/shared/Scenario1_Basics.xaml | 60 ++ .../LampArray/shared/Scenario2_Effects.xaml | 50 ++ Samples/MobileHotspot/README.md | 62 ++ .../MobileHotspot/cppwinrt/MobileHotspot.sln | 51 ++ .../cppwinrt/MobileHotspot.vcxproj | 191 ++++++ .../cppwinrt/MobileHotspot.vcxproj.filters | 59 ++ .../cppwinrt/Package.appxmanifest | 46 ++ Samples/MobileHotspot/cppwinrt/Project.idl | 25 + .../cppwinrt/SampleConfiguration.cpp | 133 ++++ .../cppwinrt/SampleConfiguration.h | 20 + .../Scenario1_ConfigureMobileHotspot.cpp | 103 +++ .../Scenario1_ConfigureMobileHotspot.h | 47 ++ .../Scenario2_ToggleMobileHotspot.cpp | 110 ++++ .../cppwinrt/Scenario2_ToggleMobileHotspot.h | 36 ++ .../MobileHotspot/cppwinrt/packages.config | 4 + Samples/MobileHotspot/cppwinrt/pch.cpp | 6 + Samples/MobileHotspot/cppwinrt/pch.h | 23 + Samples/MobileHotspot/cs/MobileHotspot.csproj | 183 ++++++ Samples/MobileHotspot/cs/MobileHotspot.sln | 59 ++ Samples/MobileHotspot/cs/Package.appxmanifest | 46 ++ .../MobileHotspot/cs/SampleConfiguration.cs | 150 +++++ .../Scenario1_ConfigureMobileHotspot.xaml.cs | 100 +++ .../cs/Scenario2_ToggleMobileHotspot.xaml.cs | 108 ++++ .../Scenario1_ConfigureMobileHotspot.xaml | 55 ++ .../shared/Scenario2_ToggleMobileHotspot.xaml | 45 ++ Samples/WebAuthenticationBroker/README.md | 13 +- .../cpp/SampleConfiguration.cpp | 1 - .../cpp/Scenario1_oAuthGoogle.xaml | 61 -- .../cpp/Scenario1_oAuthGoogle.xaml.cpp | 74 --- .../cpp/Scenario1_oAuthGoogle.xaml.h | 18 - .../cpp/WebAuthenticationBroker.vcxproj | 7 - .../WebAuthenticationBroker.vcxproj.filters | 1 - .../cs/SampleConfiguration.cs | 1 - .../cs/Scenario4_Google.xaml | 61 -- .../cs/Scenario4_Google.xaml.cs | 64 -- .../cs/WebAuthenticationBroker.csproj | 7 - 58 files changed, 4578 insertions(+), 313 deletions(-) create mode 100644 Samples/LampArray/README.md create mode 100644 Samples/LampArray/cppwinrt/LampArray.sln create mode 100644 Samples/LampArray/cppwinrt/LampArray.vcxproj create mode 100644 Samples/LampArray/cppwinrt/LampArray.vcxproj.filters create mode 100644 Samples/LampArray/cppwinrt/Package.appxmanifest create mode 100644 Samples/LampArray/cppwinrt/Project.idl create mode 100644 Samples/LampArray/cppwinrt/SampleConfiguration.cpp create mode 100644 Samples/LampArray/cppwinrt/SampleConfiguration.h create mode 100644 Samples/LampArray/cppwinrt/Scenario1_Basics.cpp create mode 100644 Samples/LampArray/cppwinrt/Scenario1_Basics.h create mode 100644 Samples/LampArray/cppwinrt/Scenario2_Effects.cpp create mode 100644 Samples/LampArray/cppwinrt/Scenario2_Effects.h create mode 100644 Samples/LampArray/cppwinrt/packages.config create mode 100644 Samples/LampArray/cppwinrt/pch.cpp create mode 100644 Samples/LampArray/cppwinrt/pch.h create mode 100644 Samples/LampArray/cs/LampArray.csproj create mode 100644 Samples/LampArray/cs/LampArray.sln create mode 100644 Samples/LampArray/cs/Package.appxmanifest create mode 100644 Samples/LampArray/cs/SampleConfiguration.cs create mode 100644 Samples/LampArray/cs/Scenario1_Basics.xaml.cs create mode 100644 Samples/LampArray/cs/Scenario2_Effects.xaml.cs create mode 100644 Samples/LampArray/shared/Scenario1_Basics.xaml create mode 100644 Samples/LampArray/shared/Scenario2_Effects.xaml create mode 100644 Samples/MobileHotspot/README.md create mode 100644 Samples/MobileHotspot/cppwinrt/MobileHotspot.sln create mode 100644 Samples/MobileHotspot/cppwinrt/MobileHotspot.vcxproj create mode 100644 Samples/MobileHotspot/cppwinrt/MobileHotspot.vcxproj.filters create mode 100644 Samples/MobileHotspot/cppwinrt/Package.appxmanifest create mode 100644 Samples/MobileHotspot/cppwinrt/Project.idl create mode 100644 Samples/MobileHotspot/cppwinrt/SampleConfiguration.cpp create mode 100644 Samples/MobileHotspot/cppwinrt/SampleConfiguration.h create mode 100644 Samples/MobileHotspot/cppwinrt/Scenario1_ConfigureMobileHotspot.cpp create mode 100644 Samples/MobileHotspot/cppwinrt/Scenario1_ConfigureMobileHotspot.h create mode 100644 Samples/MobileHotspot/cppwinrt/Scenario2_ToggleMobileHotspot.cpp create mode 100644 Samples/MobileHotspot/cppwinrt/Scenario2_ToggleMobileHotspot.h create mode 100644 Samples/MobileHotspot/cppwinrt/packages.config create mode 100644 Samples/MobileHotspot/cppwinrt/pch.cpp create mode 100644 Samples/MobileHotspot/cppwinrt/pch.h create mode 100644 Samples/MobileHotspot/cs/MobileHotspot.csproj create mode 100644 Samples/MobileHotspot/cs/MobileHotspot.sln create mode 100644 Samples/MobileHotspot/cs/Package.appxmanifest create mode 100644 Samples/MobileHotspot/cs/SampleConfiguration.cs create mode 100644 Samples/MobileHotspot/cs/Scenario1_ConfigureMobileHotspot.xaml.cs create mode 100644 Samples/MobileHotspot/cs/Scenario2_ToggleMobileHotspot.xaml.cs create mode 100644 Samples/MobileHotspot/shared/Scenario1_ConfigureMobileHotspot.xaml create mode 100644 Samples/MobileHotspot/shared/Scenario2_ToggleMobileHotspot.xaml delete mode 100644 Samples/WebAuthenticationBroker/cpp/Scenario1_oAuthGoogle.xaml delete mode 100644 Samples/WebAuthenticationBroker/cpp/Scenario1_oAuthGoogle.xaml.cpp delete mode 100644 Samples/WebAuthenticationBroker/cpp/Scenario1_oAuthGoogle.xaml.h delete mode 100644 Samples/WebAuthenticationBroker/cs/Scenario4_Google.xaml delete mode 100644 Samples/WebAuthenticationBroker/cs/Scenario4_Google.xaml.cs diff --git a/README.md b/README.md index b9b0c1f8a9..f833841027 100644 --- a/README.md +++ b/README.md @@ -304,11 +304,11 @@ For additional Windows samples, see [Windows on GitHub](http://microsoft.github. General Purpose Input/Output (GPIO) Gyrometer - Presence sensor + Inclinometer - Inclinometer Inter-Integrated Circuit (I2C) + Lamp array Lamp device @@ -327,13 +327,16 @@ For additional Windows samples, see [Windows on GitHub](http://microsoft.github. POS printer + Presence sensor Proximity sensor Radial controller - Relative inclinometer + Relative inclinometer Serial Arduino Serial Peripheral Interface (SPI) + + Simple orientation sensor @@ -509,24 +512,25 @@ For additional Windows samples, see [Windows on GitHub](http://microsoft.github. JSON Mobile broadband - Network connectivity + Mobile hotspot + Network connectivity On-Demand Wi-Fi hotspot Radios - Socket activity trigger stream socket + Socket activity trigger stream socket StreamSocket Syndication - USSD protocol + USSD protocol WebSocket Wi-Fi Direct - Wi-Fi Direct services + Wi-Fi Direct services Wi-Fi hotspot authentication Wi-Fi scanning diff --git a/Samples/LampArray/README.md b/Samples/LampArray/README.md new file mode 100644 index 0000000000..506b882f25 --- /dev/null +++ b/Samples/LampArray/README.md @@ -0,0 +1,85 @@ +--- +page_type: sample +languages: +- csharp +- cppwinrt +- cpp +products: +- windows +- windows-uwp +urlFragment: LampArray +extendedZipContent: +- path: SharedContent + target: SharedContent +- path: LICENSE + target: LICENSE +description: "Shows how to control RGB lighting of compatible peripheral devices." +--- + + + +# LampArray sample + +This sample shows how to control RGB lighting of peripheral devices using the [Windows.Devices.Lights](https://learn.microsoft.com/en-us/uwp/api/windows.devices.lights) and [Windows.Devices.Lights.Effects](https://learn.microsoft.com/en-us/uwp/api/windows.devices.lights.effects) APIs. + +Supported devices conform to the [HID Lighting and Illumination Standard](https://www.usb.org/sites/default/files/hutrr84_-_lighting_and_illumination_page.pdf). + +Specifically, this sample shows how to: + +- Use [Windows.Devices.Enumeration.DeviceWatcher](https://learn.microsoft.com/en-us/uwp/api/windows.devices.enumeration.devicewatcher) with an AQS filter to register for LampArray attach and removal events. + +- Obtain instances of [Windows.Devices.Lights.LampArray](https://learn.microsoft.com/en-us/uwp/api/windows.devices.lights.lamparray) and use them to query device properties and change RGB lighting colors and brightness. + +- Create compelling RGB lighting effects using the [Windows.Devices.Lights.Effects](https://learn.microsoft.com/en-us/uwp/api/windows.devices.lights.effects) APIs. + +- Create an app package with the "com.microsoft.windows.lighting" AppExtension. The sample will assume control of lighting when in the foreground. Declaring the "com.microsoft.windows.lighting" AppExtension will allow the sample to appear in the Dynamic Lighting page of Windows Settings, where it can be prioritized for lighting control when not in the foreground (supported on Windows build 23466 and above). + +**Note** The Windows universal samples require Visual Studio to build and Windows 10 to execute. + +To obtain information about Windows 10 development, go to the [Windows Dev Center](http://go.microsoft.com/fwlink/?LinkID=532421). + +To obtain information about Microsoft Visual Studio and the tools for developing Windows apps, go to [Visual Studio](http://go.microsoft.com/fwlink/?LinkID=532422). + +### Reference + +[Windows.Devices.Lights](https://learn.microsoft.com/en-us/uwp/api/windows.devices.lights) + +[Windows.Devices.Lights.Effects](https://learn.microsoft.com/en-us/uwp/api/windows.devices.lights.effects) + +[Windows.Devices.Enumeration.DeviceWatcher](https://learn.microsoft.com/en-us/uwp/api/windows.devices.enumeration.devicewatcher) + +### Conceptual + +[HID Lighting and Illumination Standard](https://www.usb.org/sites/default/files/hutrr84_-_lighting_and_illumination_page.pdf) + +[Dynamic Lighting](https://learn.microsoft.com/en-us/windows/uwp/devices-sensors/lighting-dynamic-lamparray) - This page also describes how to declare the "com.microsoft.windows.lighting" AppExtension for background (ambient) lighting control. + +[Dynamic lighting devices](https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/dynamic-lighting-devices) - This page contains a list of devices known to be compatible with the above HID Lighting standard. + +## System requirements + +**Client:** Windows 10, version 17763 or above. + +**Server:** Not supported. + +## Build the sample + +1. If you download the samples ZIP, be sure to unzip the entire archive, not just the folder with the sample you want to build. +2. Start Microsoft Visual Studio and select **File** \> **Open** \> **Project/Solution**. +3. Starting in the folder where you unzipped the samples, go to the Samples subfolder, then the subfolder for this specific sample, then the subfolder for your preferred language (C++ or C#). Double-click the Visual Studio Solution (.sln) file. +4. Press Ctrl+Shift+B, or select **Build** \> **Build Solution**. + +## Run the sample + +The next steps depend on whether you just want to deploy the sample or you want to both deploy and run it. + +### Deploying the sample + +- Select Build > Deploy Solution. + +### Deploying and running the sample + +- To debug the sample and then run it, press F5 or select Debug > Start Debugging. To run the sample without debugging, press Ctrl+F5 or select Debug > Start Without Debugging. diff --git a/Samples/LampArray/cppwinrt/LampArray.sln b/Samples/LampArray/cppwinrt/LampArray.sln new file mode 100644 index 0000000000..63ca61081c --- /dev/null +++ b/Samples/LampArray/cppwinrt/LampArray.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33723.286 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LampArray", "LampArray.vcxproj", "{6619AD8E-72C9-492A-BC8A-2AF1995E226E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM = Release|ARM + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Debug|ARM.ActiveCfg = Debug|ARM + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Debug|ARM.Build.0 = Debug|ARM + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Debug|ARM.Deploy.0 = Debug|ARM + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Debug|x64.ActiveCfg = Debug|x64 + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Debug|x64.Build.0 = Debug|x64 + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Debug|x64.Deploy.0 = Debug|x64 + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Debug|x86.ActiveCfg = Debug|Win32 + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Debug|x86.Build.0 = Debug|Win32 + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Debug|x86.Deploy.0 = Debug|Win32 + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Release|ARM.ActiveCfg = Release|ARM + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Release|ARM.Build.0 = Release|ARM + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Release|ARM.Deploy.0 = Release|ARM + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Release|x64.ActiveCfg = Release|x64 + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Release|x64.Build.0 = Release|x64 + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Release|x64.Deploy.0 = Release|x64 + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Release|x86.ActiveCfg = Release|Win32 + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Release|x86.Build.0 = Release|Win32 + {6619AD8E-72C9-492A-BC8A-2AF1995E226E}.Release|x86.Deploy.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9FD4241E-48DF-497E-B1AD-92AA02ED08AD} + EndGlobalSection +EndGlobal diff --git a/Samples/LampArray/cppwinrt/LampArray.vcxproj b/Samples/LampArray/cppwinrt/LampArray.vcxproj new file mode 100644 index 0000000000..098322fd46 --- /dev/null +++ b/Samples/LampArray/cppwinrt/LampArray.vcxproj @@ -0,0 +1,193 @@ + + + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), LICENSE))\SharedContent + + + true + {6619AD8E-72C9-492A-BC8A-2AF1995E226E} + LampArray + SDKTemplate + en-US + 15.0 + true + Windows Store + 10.0 + 10.0.22621.0 + $(WindowsTargetPlatformVersion) + + + + + Debug + ARM + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + Win32 + + + Release + x64 + + + + Application + Unicode + + + true + true + + + false + true + false + + + + + + + + $(VC_IncludePath);$(UniversalCRT_IncludePath);$(WindowsSDK_IncludePath);$(SharedContentDir)\cppwinrt + true + + + + Use + pch.h + $(IntDir)pch.pch + Level4 + %(AdditionalOptions) /bigobj + 4453;28204 + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + NDEBUG;%(PreprocessorDefinitions) + + + + + $(SharedContentDir)\xaml\App.xaml + + + $(SharedContentDir)\xaml\MainPage.xaml + + + + ..\shared\Scenario1_Basics.xaml + + + ..\shared\Scenario2_Effects.xaml + + + + + + Designer + + + Designer + + + + + Styles\Styles.xaml + + + + + $(SharedContentDir)\xaml\App.xaml + + + $(SharedContentDir)\xaml\MainPage.xaml + + + SampleConfiguration.h + + + ..\shared\Scenario1_Basics.xaml + + + ..\shared\Scenario2_Effects.xaml + + + Create + pch.h + + + Project.idl + + + + + $(SharedContentDir)\xaml\MainPage.xaml + + + + + + Designer + + + + + + Assets\microsoft-sdk.png + + + Assets\smallTile-sdk.png + + + Assets\splash-sdk.png + + + Assets\squareTile-sdk.png + + + Assets\storeLogo-sdk.png + + + Assets\tile-sdk.png + + + Assets\windows-sdk.png + + + Assets\grapes.jpg + + + + + + + + + + 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/LampArray/cppwinrt/LampArray.vcxproj.filters b/Samples/LampArray/cppwinrt/LampArray.vcxproj.filters new file mode 100644 index 0000000000..acae9d4abd --- /dev/null +++ b/Samples/LampArray/cppwinrt/LampArray.vcxproj.filters @@ -0,0 +1,70 @@ + + + + + 4416d50a-7676-4d0a-9b2c-91ff70c6047f + bmp;fbx;gif;jpg;jpeg;tga;tiff;tif;png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/LampArray/cppwinrt/Package.appxmanifest b/Samples/LampArray/cppwinrt/Package.appxmanifest new file mode 100644 index 0000000000..ba4493188c --- /dev/null +++ b/Samples/LampArray/cppwinrt/Package.appxmanifest @@ -0,0 +1,49 @@ + + + + + + LampArray C++/WinRT Sample + Microsoft Corporation + Assets\storelogo-sdk.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/LampArray/cppwinrt/Project.idl b/Samples/LampArray/cppwinrt/Project.idl new file mode 100644 index 0000000000..d16e7b5781 --- /dev/null +++ b/Samples/LampArray/cppwinrt/Project.idl @@ -0,0 +1,25 @@ +//********************************************************* +// +// 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. +// +//********************************************************* + +namespace SDKTemplate +{ + [default_interface] + runtimeclass Scenario1_Basics : Windows.UI.Xaml.Controls.Page + { + Scenario1_Basics(); + } + + [default_interface] + runtimeclass Scenario2_Effects : Windows.UI.Xaml.Controls.Page + { + Scenario2_Effects(); + } +} diff --git a/Samples/LampArray/cppwinrt/SampleConfiguration.cpp b/Samples/LampArray/cppwinrt/SampleConfiguration.cpp new file mode 100644 index 0000000000..ce29cf6886 --- /dev/null +++ b/Samples/LampArray/cppwinrt/SampleConfiguration.cpp @@ -0,0 +1,60 @@ +//********************************************************* +// +// 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 "MainPage.h" + +using namespace winrt; +using namespace winrt::SDKTemplate; +using namespace winrt::Windows::Devices::Lights; +using namespace winrt::Windows::Foundation::Collections; + +hstring winrt::to_hstring(LampArrayKind lampArrayKind) +{ + switch (lampArrayKind) + { + case LampArrayKind::Keyboard: + return L"Keyboard"; + case LampArrayKind::Mouse: + return L"Mouse"; + case LampArrayKind::GameController: + return L"Game Controller"; + case LampArrayKind::Peripheral: + return L"Peripheral"; + case LampArrayKind::Scene: + return L"Scene"; + case LampArrayKind::Notification: + return L"Notification"; + case LampArrayKind::Chassis: + return L"Chassis"; + case LampArrayKind::Wearable: + return L"Wearable"; + case LampArrayKind::Furniture: + return L"Furniture"; + case LampArrayKind::Art: + return L"Art"; + case LampArrayKind::Undefined: + default: + return L"Unknown LampArrayKind"; + } +} + +hstring implementation::MainPage::FEATURE_NAME() +{ + return L"LampArray C++/WinRT Sample"; +} + +IVector implementation::MainPage::scenariosInner = winrt::single_threaded_observable_vector( +{ + Scenario{ L"LampArray Basics", xaml_typename() }, + Scenario{ L"LampArray Effects", xaml_typename() }, +}); diff --git a/Samples/LampArray/cppwinrt/SampleConfiguration.h b/Samples/LampArray/cppwinrt/SampleConfiguration.h new file mode 100644 index 0000000000..a8906373d0 --- /dev/null +++ b/Samples/LampArray/cppwinrt/SampleConfiguration.h @@ -0,0 +1,33 @@ +//********************************************************* +// +// 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 "pch.h" + +namespace winrt +{ + hstring to_hstring(winrt::Windows::Devices::Lights::LampArrayKind lampArrayKind); +} + +namespace winrt::SDKTemplate +{ + struct LampArrayInfo + { + LampArrayInfo(winrt::hstring const& id, winrt::hstring const& displayName, winrt::Windows::Devices::Lights::LampArray const& lampArray) + : id(id), displayName(displayName), lampArray(lampArray) + { + } + + winrt::hstring const id; + winrt::hstring const displayName; + winrt::Windows::Devices::Lights::LampArray const lampArray; + }; +} diff --git a/Samples/LampArray/cppwinrt/Scenario1_Basics.cpp b/Samples/LampArray/cppwinrt/Scenario1_Basics.cpp new file mode 100644 index 0000000000..39c15fe197 --- /dev/null +++ b/Samples/LampArray/cppwinrt/Scenario1_Basics.cpp @@ -0,0 +1,221 @@ +//********************************************************* +// +// 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 "Scenario1_Basics.h" +#include "Scenario1_Basics.g.cpp" + +using namespace winrt; +using namespace winrt::Windows::Devices::Enumeration; +using namespace winrt::Windows::Devices::Lights; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::System; +using namespace winrt::Windows::UI; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml::Navigation; + +namespace winrt::SDKTemplate::implementation +{ + Scenario1_Basics::Scenario1_Basics() + { + InitializeComponent(); + } + + void Scenario1_Basics::OnNavigatedTo(NavigationEventArgs const&) + { + UpdateLampArrayList(); + + // Start watching for newly attached LampArrays. + m_deviceWatcher = DeviceInformation::CreateWatcher(LampArray::GetDeviceSelector()); + + m_deviceAddedRevoker = m_deviceWatcher.Added(winrt::auto_revoke, { get_weak(), &Scenario1_Basics::Watcher_Added}); + m_deviceRemovedRevoker = m_deviceWatcher.Removed(winrt::auto_revoke, { get_weak(), &Scenario1_Basics::Watcher_Removed }); + + m_deviceWatcher.Start(); + } + + void Scenario1_Basics::OnNavigatedFrom(NavigationEventArgs const&) + { + m_deviceWatcher.Stop(); + } + + winrt::fire_and_forget Scenario1_Basics::Watcher_Added(DeviceWatcher const&, DeviceInformation const& deviceInformation) + { + auto lifetime = get_strong(); + + auto info = std::make_shared( + deviceInformation.Id(), + deviceInformation.Name(), + LampArray::FromIdAsync(deviceInformation.Id()).get()); + + // DeviceWatcher events are invoked on a background thread. + // We need to switch to the foreground thread to update our app UI + // and access member variables without race conditions. + + co_await winrt::resume_foreground(Dispatcher()); + + if (info->lampArray == nullptr) + { + // A LampArray was found, but Windows couldn't initialize it. + // This suggests a device error. + rootPage.NotifyUser(L"Problem with LampArray " + info->displayName + L".", NotifyType::ErrorMessage); + co_return; + } + + // Remember this new LampArray and add it to the list. + m_attachedLampArrays.push_back(std::move(info)); + UpdateLampArrayList(); + } + + winrt::fire_and_forget Scenario1_Basics::Watcher_Removed(DeviceWatcher const&, DeviceInformationUpdate deviceInformationUpdate) + { + auto lifetime = get_strong(); + + // DeviceWatcher events are invoked on a background thread. + // We need to switch to the foreground thread to update our app UI + // and access member variables without race conditions. + co_await winrt::resume_foreground(Dispatcher()); + + m_attachedLampArrays.erase( + std::remove_if(m_attachedLampArrays.begin(), m_attachedLampArrays.end(), [id = deviceInformationUpdate.Id()](auto&& info) { return info->id == id; }), + m_attachedLampArrays.end()); + + UpdateLampArrayList(); + } + + void Scenario1_Basics::UpdateLampArrayList() + { + std::wstring message = std::wstring(L"Attached LampArrays: ") + std::to_wstring(m_attachedLampArrays.size()) + L"\n"; + + for (auto&& info : m_attachedLampArrays) + { + message += std::wstring(L"\t") + std::wstring(info->displayName) + L" (" + + winrt::to_hstring(info->lampArray.LampArrayKind()) + L", " + std::to_wstring(info->lampArray.LampCount()) + L" lamps)\n"; + } + + LampArraysSummary().Text(message); + } + + void Scenario1_Basics::Apply_Clicked(IInspectable const&, RoutedEventArgs const&) + { + for (auto&& info : m_attachedLampArrays) + { + ApplySettingsToLampArray(info->lampArray); + } + } + + void Scenario1_Basics::ApplySettingsToLampArray(LampArray const& lampArray) + { + // Apply the light pattern. + if (OffButton().IsChecked().Value()) + { + // Set all Lamps on the LampArray to black, turning them off. + lampArray.SetColor(Colors::Black()); + } + else if (SetColorButton().IsChecked().Value()) + { + // Set all Lamps on the LampArray to green. + lampArray.SetColor(Colors::Green()); + } + else if (GradientButton().IsChecked().Value()) + { + SetGradientPatternToLampArray(lampArray); + } + else if (WasdButton().IsChecked().Value()) + { + SetWasdPatternToLampArray(lampArray); + } + + // Apply the brightness. Convert the percentage value in the slider to the range [0, 1] expected by BrightnessLevel. + lampArray.BrightnessLevel(BrightnessSlider().Value() / BrightnessSlider().Maximum()); + } + + void Scenario1_Basics::SetGradientPatternToLampArray(LampArray const& lampArray) + { + // Create a static gradient on the LampArray from black to red, blending from left to right. + + // For this effect, we want to set the colors of all Lamps. Create an array containing all the Lamp indices + // and an array of Colors. Both arrays must be of the same length. We will use these for SetColorsForIndices later. + std::vector indices(lampArray.LampCount()); + std::iota(indices.begin(), indices.end(), 0); + + std::vector colors(lampArray.LampCount()); + + // A LampArray provides information about its size using a three-dimensional logical bounding box, + // as well as the location of each of its Lamps within that bounding box. + // The origin point of the bounding box is the leftmost (X), farthest (Y), uppermost (Z) corner relative to the user. + // LampArray bounding boxes and Lamp positions are measured in meters. + + // Because our gradient will blend from left to right, we will set the Lamp colors according to each Lamp's X position. + // We will make two passes over the LampArray. First, calculate the minimum and maximum X position values of all Lamps. + // This is to "trim" the margins of the bounding box where no Lamps are present. + double maxX = 0; + double minX = lampArray.BoundingBox().x; + + for (int i : indices) + { + LampInfo lampInfo = lampArray.GetLampInfo(i); + + if (lampInfo.Position().x > maxX) + { + maxX = lampInfo.Position().x; + } + + if (lampInfo.Position().x < minX) + { + minX = lampInfo.Position().x; + } + } + + // In the second pass, we will calculate the gradient colors for each Lamp. + for (int i : indices) + { + LampInfo lampInfo = lampArray.GetLampInfo(i); + + // Calculate the X position for this Lamp relative to the rightmost Lamp. We will scale the Lamp's color by this relative value below. + // (We also want to avoid dividing by zero here in case all Lamps share the same X position, including the single-Lamp case.) + double xProgress = 1.0; + if (maxX != minX) + { + xProgress = (lampInfo.Position().x - minX) / (maxX - minX); + } + + // Apply that value to the expected color for the lamp. + // Maximize the Alpha value to give the color full opacity. + colors[i].A = std::numeric_limits::max(); + + // Set the color's R value based on the Lamp's X position. + // As the X position of the Lamp increases (i.e., is further to the right), + // the Red value of the Lamp's color will also increase. + // This means that the leftmost Lamp(s) will be black (turned off), and the rightmost Lamp(s) will be the brightest red. + colors[i].R = (uint8_t)(xProgress * std::numeric_limits::max()); + } + + lampArray.SetColorsForIndices(colors, indices); + } + + void Scenario1_Basics::SetWasdPatternToLampArray(LampArray const& lampArray) + { + // Set a background color of blue. + lampArray.SetColor(Colors::Blue()); + + // Highlight the WASD keys in white, if the LampArray supports addressing its Lamps using a virtual key to Lamp mapping. + // This is typically found on keyboard LampArrays. Other LampArrays will not usually support virtual key based lighting. + if (lampArray.SupportsVirtualKeys()) + { + auto white = Colors::White(); + std::array colors{ white, white, white, white }; + std::array virtualKeys{ VirtualKey::W, VirtualKey::A, VirtualKey::S, VirtualKey::D }; + + lampArray.SetColorsForKeys(colors, virtualKeys); + } + } +} diff --git a/Samples/LampArray/cppwinrt/Scenario1_Basics.h b/Samples/LampArray/cppwinrt/Scenario1_Basics.h new file mode 100644 index 0000000000..9cf7de2c25 --- /dev/null +++ b/Samples/LampArray/cppwinrt/Scenario1_Basics.h @@ -0,0 +1,61 @@ +//********************************************************* +// +// 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 "Scenario1_Basics.g.h" + +namespace winrt::SDKTemplate::implementation +{ + struct Scenario1_Basics : Scenario1_BasicsT + { + public: + Scenario1_Basics(); + + void OnNavigatedTo(winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs const&); + void OnNavigatedFrom(winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs const&); + + void Apply_Clicked( + winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + + private: + SDKTemplate::MainPage rootPage{ MainPage::Current() }; + winrt::Windows::Devices::Enumeration::DeviceWatcher m_deviceWatcher{ nullptr }; + + // Currently attached LampArrays + std::vector> m_attachedLampArrays; + + winrt::Windows::Devices::Enumeration::DeviceWatcher::Added_revoker m_deviceAddedRevoker; + winrt::Windows::Devices::Enumeration::DeviceWatcher::Removed_revoker m_deviceRemovedRevoker; + + winrt::fire_and_forget Watcher_Added( + winrt::Windows::Devices::Enumeration::DeviceWatcher const&, + winrt::Windows::Devices::Enumeration::DeviceInformation const& deviceInformation); + + winrt::fire_and_forget Watcher_Removed( + winrt::Windows::Devices::Enumeration::DeviceWatcher const&, + winrt::Windows::Devices::Enumeration::DeviceInformationUpdate deviceInformationUpdate); + + void UpdateLampArrayList(); + + void ApplySettingsToLampArray(winrt::Windows::Devices::Lights::LampArray const& lampArray); + void SetGradientPatternToLampArray(winrt::Windows::Devices::Lights::LampArray const& lampArray); + void SetWasdPatternToLampArray(winrt::Windows::Devices::Lights::LampArray const& lampArray); + }; +} + +namespace winrt::SDKTemplate::factory_implementation +{ + struct Scenario1_Basics : Scenario1_BasicsT + { + }; +} diff --git a/Samples/LampArray/cppwinrt/Scenario2_Effects.cpp b/Samples/LampArray/cppwinrt/Scenario2_Effects.cpp new file mode 100644 index 0000000000..30fe0d54d2 --- /dev/null +++ b/Samples/LampArray/cppwinrt/Scenario2_Effects.cpp @@ -0,0 +1,592 @@ +//********************************************************* +// +// 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 "Scenario2_Effects.h" +#include "Scenario2_Effects.g.cpp" + +using namespace winrt; +using namespace winrt::Microsoft::Graphics::Canvas; +using namespace winrt::Windows::Devices::Enumeration; +using namespace winrt::Windows::Devices::Lights; +using namespace winrt::Windows::Devices::Lights::Effects; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Graphics::Imaging; +using namespace winrt::Windows::Storage; +using namespace winrt::Windows::Storage::Streams; +using namespace winrt::Windows::UI; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml::Controls::Primitives; +using namespace winrt::Windows::UI::Xaml::Media::Imaging; +using namespace winrt::Windows::UI::Xaml::Navigation; + +namespace +{ + inline constexpr TimeSpan EffectUpdateInterval = std::chrono::milliseconds(33); + inline constexpr TimeSpan ColorRampDuration = std::chrono::milliseconds(500); + inline constexpr int CustomEffectFrameCount = 30; + inline constexpr int GeneratedBitmapEffectFrameCount = 60; +} + +namespace winrt::SDKTemplate::implementation +{ + Scenario2_Effects::Scenario2_Effects() + { + InitializeComponent(); + } + + void Scenario2_Effects::OnNavigatedTo(NavigationEventArgs const&) + { + UpdateLampArrayList(); + + // Start watching for newly attached LampArrays. + m_deviceWatcher = DeviceInformation::CreateWatcher(LampArray::GetDeviceSelector()); + + m_deviceAddedRevoker = m_deviceWatcher.Added(winrt::auto_revoke, { get_weak(), &Scenario2_Effects::Watcher_Added }); + m_deviceRemovedRevoker = m_deviceWatcher.Removed(winrt::auto_revoke, { get_weak(), &Scenario2_Effects::Watcher_Removed }); + + m_deviceWatcher.Start(); + } + + void Scenario2_Effects::OnNavigatedFrom(NavigationEventArgs const&) + { + m_deviceWatcher.Stop(); + + // When changing scenarios, stop our effects to prevent them from interfering with the new scenario. + CleanupPreviousEffect(); + } + + winrt::fire_and_forget Scenario2_Effects::Watcher_Added(DeviceWatcher const&, DeviceInformation const& deviceInformation) + { + auto lifetime = get_strong(); + + auto info = std::make_unique( + deviceInformation.Id(), + deviceInformation.Name(), + LampArray::FromIdAsync(deviceInformation.Id()).get()); + + // DeviceWatcher events are invoked on a background thread. + // We need to switch to the foreground thread to update our app UI + // and access member variables without race conditions. + + co_await winrt::resume_foreground(Dispatcher()); + + if (info->lampArray == nullptr) + { + // A LampArray was found, but Windows couldn't initialize it. + // This suggests a device error. + rootPage.NotifyUser(L"Problem with LampArray " + info->displayName + L".", NotifyType::ErrorMessage); + co_return; + } + + // Initial condition for the new LampArray is all lights off. + info->lampArray.SetColor(Colors::Black()); + + m_attachedLampArrays.push_back(std::move(info)); + + UpdateLampArrayList(); + } + + winrt::fire_and_forget Scenario2_Effects::Watcher_Removed(DeviceWatcher const&, DeviceInformationUpdate deviceInformationUpdate) + { + auto lifetime = get_strong(); + + // DeviceWatcher events are invoked on a background thread. + // We need to switch to the foreground thread to update our app UI + // and access member variables without race conditions. + co_await winrt::resume_foreground(Dispatcher()); + + m_attachedLampArrays.erase( + std::remove_if(m_attachedLampArrays.begin(), m_attachedLampArrays.end(), [id = deviceInformationUpdate.Id()](auto&& info) { return info->id == id; }), + m_attachedLampArrays.end()); + + UpdateLampArrayList(); + } + + void Scenario2_Effects::UpdateLampArrayList() + { + std::wstring message = std::wstring(L"Attached LampArrays: ") + std::to_wstring(m_attachedLampArrays.size()) + L"\n"; + + for (auto&& info : m_attachedLampArrays) + { + message += L"\t" + info->displayName + L" (" + + winrt::to_hstring(info->lampArray.LampArrayKind()) + L", " + std::to_wstring(info->lampArray.LampCount()) + L" lamps)\n"; + } + + LampArraysSummary().Text(message); + } + + // This method will be called when a new effect is selected. It will stop any running + // LampArrayEffectPlaylists in progress and reset LampArray state in preparation for the new effect. + void Scenario2_Effects::CleanupPreviousEffect() + { + // Clear the bitmap image in the app UI, as it is no longer current. + ImageBitmap().Source(nullptr); + + // Passing all the playlists to a single call to StopAll will stop them + // simultaneously across all attached LampArrays. + LampArrayEffectPlaylist::StopAll(m_readyPlaylists); + m_readyPlaylists = m_emptyList; + } + +#pragma region Static bitmap effect + winrt::fire_and_forget Scenario2_Effects::StaticBitmapButton_Click(IInspectable const&, RoutedEventArgs const&) + { + auto lifetime = get_strong(); + + CleanupPreviousEffect(); + + // This effect will display a specified bitmap image on all connected LampArrays. + // The image will be stretched and scaled to fit the Lamp dimensions of each LampArray. + + // First, open the desired bitmap file and convert it to a SoftwareBitmap + auto bitmapUri = Uri(L"ms-appx:///Assets/grapes.jpg"); + + StorageFile file = co_await StorageFile::GetFileFromApplicationUriAsync(bitmapUri); + IRandomAccessStream stream = co_await file.OpenAsync(FileAccessMode::Read); + BitmapDecoder decoder = co_await BitmapDecoder::CreateAsync(stream); + SoftwareBitmap bitmap = co_await decoder.GetSoftwareBitmapAsync(); + stream.Close(); + + // Show our bitmap image in the app UI + ImageBitmap().Source(BitmapImage(bitmapUri)); + + // Gather the new LampArrayEffectPlaylists in this collection so we can start them simultaneously. + std::vector playlists; + + // Create the effect playlist for each LampArray and add it to the collection. + for (auto&& info : m_attachedLampArrays) + { + LampArrayBitmapEffect bitmapEffect(info->lampArray, GetLampArrayIndices(info->lampArray)); + + // We want to run this effect until is stopped. For the purposes of this sample, + // set the effect Duration to run as long as possible. + bitmapEffect.Duration(TimeSpan::max()); + + // The UpdateInterval specifies how frequently we want to receive our callback function. + // Since this effect will display a static image, we only need to update once. + // Set this interval to be the same as the effect duration. + bitmapEffect.UpdateInterval(TimeSpan::max()); + + // Set the event handler for our effect. Use a helper function to + // to specify a bitmap to display on the device. + bitmapEffect.BitmapRequested([bitmap](auto&&, auto&& args) + { + StaticBitmapEffect_BitmapRequested(bitmap, args); + }); + + // Create a playlist consisting of our static bitmap effect. + LampArrayEffectPlaylist playlist; + playlist.Append(bitmapEffect); + + // Add it to the list of playlists to start simultaneously. + playlists.push_back(playlist); + } + + // Start the newly created effect playlists simultaneously. + auto playlistsView = winrt::single_threaded_vector(std::move(playlists)).GetView(); + LampArrayEffectPlaylist::StartAll(playlistsView); + + // Remember the playlists so we can stop them later. + m_readyPlaylists = playlistsView; + } + + // Helper function for BitmapRequested event handler that + // specifies the bitmap to display on the device. + // The frequency of the effect callback is customizable. + // The frequency of the event is customizable. + // As configured above when setting up the static bitmap effect, this event will + // be raised only once per effect iteration, since we set the update interval + // equal to the effect duration. + void Scenario2_Effects::StaticBitmapEffect_BitmapRequested( + SoftwareBitmap const& bitmap, + LampArrayBitmapRequestedEventArgs const& args) + { + // In a BitmapRequested handler, we can change the bitmap here to produce animations. + // For the static bitmap effect, show the same image each time. + args.UpdateBitmap(bitmap); + } +#pragma endregion + +#pragma region Fade effect + void Scenario2_Effects::FadeButton_Click(IInspectable const&, RoutedEventArgs const&) + { + CleanupPreviousEffect(); + + // This effect will set each Lamp to a random color and blink all Lamps simultaneously. + // LampArrayBlinkEffect only supports a single color at a time. To create the desired effect with + // different colors for each Lamp, we will create a separate LampArrayBlinkEffect for each Lamp, + // specifying that we only want the effect to apply to a single Lamp index. + // We will then add each Lamp effect to the playlist for its corresponding LampArray and configure the + // playlist to run its effects simultaneously instead of sequentially. + + // Gather the new LampArrayEffectPlaylists in this collection so we can start them simultaneously. + std::vector playlists; + + auto uniform_byte = std::uniform_int_distribution(0, 255); + + // Create the effect playlist for each LampArray and add it to the collection. + for (auto&& info : m_attachedLampArrays) + { + LampArrayEffectPlaylist playlist; + + // Configure the playlist to run the effects simultaneously. + // This will result in all Lamps for this LampArray blinking in unison, + // even though their colors will be different. + playlist.EffectStartMode(LampArrayEffectStartMode::Simultaneous); + + for (int i = 0; i < info->lampArray.LampCount(); i++) + { + // Generate a color for each Lamp by choosing a random Red/Green/Blue combination. + auto color = ColorHelper::FromArgb( + std::numeric_limits::max(), // Maximum alpha makes it opaque + (uint8_t)(uniform_byte(m_random)), + (uint8_t)(uniform_byte(m_random)), + (uint8_t)(uniform_byte(m_random))); + + // Create the effect and apply it to a single Lamp. + LampArrayBlinkEffect blinkEffect(info->lampArray, { i }); + + blinkEffect.Color(color); + + // We can adjust the timing of the LampArrayBlinkEffect using these properties. + // Specify how long it should take for the full color to fade in from off/Black to peak intensity. + blinkEffect.AttackDuration(std::chrono::milliseconds(300)); + + // Specify how long to display the color at peak intensity. + blinkEffect.SustainDuration(std::chrono::milliseconds(500)); + + // Specify how long it should take for the full color to fade out from peak intensity to off/Black. + blinkEffect.DecayDuration(std::chrono::milliseconds(800)); + + // Specify how long the Lamps should stay dark between blink repetitions. + blinkEffect.RepetitionDelay(std::chrono::milliseconds(100)); + + // Repeat the blink sequence indefinitely until the effect playlist is stopped. + blinkEffect.RepetitionMode(LampArrayRepetitionMode::Forever); + + playlist.Append(blinkEffect); + + // Add it to the list of playlists to start simultaneously. + playlists.push_back(playlist); + } + } + + // Start the newly created effect playlists simultaneously. + auto playlistsView = winrt::single_threaded_vector(std::move(playlists)).GetView(); + LampArrayEffectPlaylist::StartAll(playlistsView); + + // Remember the playlists so we can stop them later. + m_readyPlaylists = playlistsView; + } +#pragma endregion + +#pragma region Custom effect + // Windows.Devices.Lights.Effects contains several built-in effect types. The LampArrayCustomEffect is provided for + // greater customization of lighting beyond these simple effects. It enables the SetColor controls of LampArray to + // be integrated and scheduled with other effects and effect playlists. + // This sample will demonstrate using LampArrayCustomEffect to display a simple lighting animation. + void Scenario2_Effects::CustomEffectButton_Click(IInspectable const&, RoutedEventArgs const&) + { + CleanupPreviousEffect(); + + // This effect will use LampArrayCustomEffect to create a wave-like animation. + // Lamps will turn on from left to right across the LampArray, then turn off in a similar manner. + + // Gather the new LampArrayEffectPlaylists in this collection so we can start them simultaneously. + std::vector playlists; + + // Create the effect playlist for each LampArray and add it to the collection. + for (auto&& info : m_attachedLampArrays) + { + LampArray const& lampArray = info->lampArray; + std::vector indices = GetLampArrayIndices(lampArray); + float boundingBoxWidth = lampArray.BoundingBox().x; + // Avoid division by zero when calculating normalized positions. + if (boundingBoxWidth == 0.0f) + { + boundingBoxWidth = 1.0f; + } + auto state = std::make_unique(lampArray); + + // Calculate the normalized position of each lamp. + for (int i = 0; i < state->lampNormalizedX.size(); i++) + { + auto lampInfo = lampArray.GetLampInfo(i); + state->lampNormalizedX[i] = lampInfo.Position().x / boundingBoxWidth; + } + + LampArrayCustomEffect customEffect = LampArrayCustomEffect(lampArray, indices); + + // We want to run this effect until is stopped. For the purposes of this sample, + // set the effect Duration to run as long as possible. + customEffect.Duration(TimeSpan::max()); + + // The UpdateInterval specifies how frequently we want to receive our callback function. + // For our animation, set this interval to 33 milliseconds, which equates to approximately 30 frames per second. + customEffect.UpdateInterval(EffectUpdateInterval); + + // This event handler will be called on every frame to update the animation. + customEffect.UpdateRequested([state = std::move(state)](auto&&, auto&& args) + { + CustomEffect_UpdateRequested(state.get(), args); + }); + + // Create a playlist consisting of our wave effect. + LampArrayEffectPlaylist playlist; + playlist.Append(customEffect); + + // Add it to the list of playlists to start simultaneously. + playlists.push_back(playlist); + } + + // Start the newly created effect playlists. + auto playlistsView = winrt::single_threaded_vector(std::move(playlists)).GetView(); + LampArrayEffectPlaylist::StartAll(playlistsView); + + // Remember the playlists so we can stop them later. + m_readyPlaylists = playlistsView; + } + + // Helper for the LampArrayCustomEffect.UpdateRequested event, called periodically at the requested UpdateInterval. + // This enables more complex lighting patterns, such as animations. It also supports integration and scheduling with + // other effects and effect playlists. + // As configured above, this callback will be invoked every 33 milliseconds, or about 30 frames per second. + void Scenario2_Effects::CustomEffect_UpdateRequested( + CustomEffectState* state, + winrt::Windows::Devices::Lights::Effects::LampArrayUpdateRequestedEventArgs const& args) + { + // Update the Lamp colors for the current frame. Start by calculating how far into the current iteration we are. + // We will use this to mark how far across the LampArray bounding box the effect has reached. + double effectProgress = ((state->frameNumber + 1) / static_cast(CustomEffectFrameCount)); + + // If Lamps were being turned off in the previous iteration, turn them on now, and vice versa. + Color color = state->isColorOn ? state->color : Colors::Black(); + + for (unsigned i = 0; i < state->lampNormalizedX.size(); i++) + { + // All Lamps that are to the left of our marker (normalized to the LampArray bounding box width) + // should have their states updated. + if (state->lampNormalizedX[i] <= effectProgress) + { + state->lampColors[i] = color; + } + } + + // Apply the Lamp states to the LampArray. + args.SetColorsForIndices(state->lampColors, GetLampArrayIndices(state->lampArray)); + + // Increment our frame counter, with wraparound if we've reached the frame limit. + // Also update whether the lamps should be turned on or off for the next iteration. + if (++state->frameNumber == CustomEffectFrameCount) + { + state->frameNumber = 0; + state->isColorOn = !state->isColorOn; + } + } +#pragma endregion + +#pragma region Generated bitmap effect + // This effect will use Win2D to create a simple animation. On each frame, we will + // generate a bitmap image and apply it to the entire LampArray. + // The effect illustrates dynamically creating a bitmap image and using it in a lighting effect. + // LampArrayBitmapEffect could be used for in-game lighting and/or integration with 2D graphics libraries. + void Scenario2_Effects::GeneratedBitmapButton_Click(IInspectable const&, RoutedEventArgs const&) + { + CleanupPreviousEffect(); + + // Gather the new LampArrayEffectPlaylists in this collection so we can start them simultaneously. + std::vector playlists; + + // Create the effect playlist for each LampArray and add it to the collection. + for (auto&& info : m_attachedLampArrays) + { + auto state = std::make_unique(); + + auto generatedBitmapEffect = LampArrayBitmapEffect{ info->lampArray, GetLampArrayIndices(info->lampArray) }; + + // We want to run this effect until is stopped. For the purposes of this sample, + // set the effect Duration to run as long as possible. + generatedBitmapEffect.Duration(TimeSpan::max()); + + // The UpdateInterval specifies how frequently we want to receive our callback function. + // For our animation, set this interval to 33 milliseconds, which equates to approximately 30 frames per second. + generatedBitmapEffect.UpdateInterval(EffectUpdateInterval); + + Size suggestedSize = generatedBitmapEffect.SuggestedBitmapSize(); + state->bitmapWidth = static_cast(suggestedSize.Width); + state->bitmapHeight = static_cast(suggestedSize.Height); + + // The size of the square is half the height or half the width, whichever is smaller. + state->squareLength = std::min(suggestedSize.Height, suggestedSize.Width) / 2.0f; + + // Vertically center the square. + state->squareY = (suggestedSize.Height - state->squareLength) / 2.0f; + + // Setup our Win2D helper objects. + state->canvasDevice = CanvasDevice::GetSharedDevice(); + + state->offscreenCanvas = CanvasRenderTarget(state->canvasDevice, suggestedSize.Width, suggestedSize.Height, 96.0f); + + state->offscreenDrawingSession = state->offscreenCanvas.CreateDrawingSession(); + + // The generated bitmap encompasses the entire LampArray + state->squareBitmap = SoftwareBitmap(BitmapPixelFormat::Bgra8, state->bitmapWidth, state->bitmapHeight, BitmapAlphaMode::Premultiplied); + + state->squareBuffer = Buffer(state->bitmapWidth * state->bitmapHeight * 4); + + // This event handler will be called on every frame to update the bitmap to display on the device. + generatedBitmapEffect.BitmapRequested([state = std::move(state)](auto&&, auto&& args) + { + GeneratedBitmapEffect_UpdateRequested(state.get(), args); + }); + + // Create a playlist consisting of our bitmap effect. + LampArrayEffectPlaylist playlist; + playlist.Append(generatedBitmapEffect); + + // Add it to the list of playlists to start simultaneously. + playlists.push_back(playlist); + } + + // Start the newly created effect playlists simultaneously. + auto playlistsView = winrt::single_threaded_vector(std::move(playlists)).GetView(); + LampArrayEffectPlaylist::StartAll(playlistsView); + + // Remember the playlists so we can stop them later. + m_readyPlaylists = playlistsView; + } + + // Callback function that allows us to specify a bitmap to display on the device. + // The frequency of the effect callback is customizable. + // As configured above when setting up the generated bitmap effect, this will be invoked + // every 33 milliseconds, or approximately 30 frames per second. + // To demonstrate using a LampArrayBitmapEffect to perform lighting animations, + // this callback will create an animation of a red rectangle moving left to right + // across a blue background. + void Scenario2_Effects::GeneratedBitmapEffect_UpdateRequested( + GeneratedBitmapState* state, + LampArrayBitmapRequestedEventArgs const& args) + { + // Draw a red rectangle on a blue background. + state->offscreenDrawingSession.Clear(Colors::Blue()); + + // The rectangle starts out just beyond the left edge of the keyboard, + // then moves to the right until it just leaves the keyboard, + // and then repeats. + float squareX = static_cast(state->frameNumber * (state->bitmapWidth + state->squareLength) / GeneratedBitmapEffectFrameCount) - state->squareLength; + state->offscreenDrawingSession.FillRectangle( + squareX, + state->squareY, + state->squareLength, + state->squareLength, + Colors::Red()); + + state->offscreenDrawingSession.Flush(); + + // Apply the generated bitmap to the LampArray. + state->offscreenCanvas.GetPixelBytes(state->squareBuffer); + state->squareBitmap.CopyFromBuffer(state->squareBuffer); + + args.UpdateBitmap(state->squareBitmap); + + // Advance to the next frame for the next iteration. + if (++state->frameNumber == GeneratedBitmapEffectFrameCount) + { + state->frameNumber = 0; + } + } +#pragma endregion + +#pragma region Color cycle effect + void Scenario2_Effects::CycleButton_Click(IInspectable const&, RoutedEventArgs const&) + { + CleanupPreviousEffect(); + + // This effect will use LampArrayColorRampEffect to perform a rainbow cycle pattern on all Lamps. + // We will do this for each LampArray by creating a LampArrayEffectPlaylist containing four LampArrayColorRampEffects. + // Each effect will blend from the previously set color to a new color over a 500ms interval. + + // Gather the new LampArrayEffectPlaylists in this collection so we can start them simultaneously. + std::vector playlists; + + // Create the effect playlist for each LampArray and add it to the collection. + for (auto&& info : m_attachedLampArrays) + { + // Create a playlist to hold our effects. + LampArrayEffectPlaylist playlist; + playlist.RepetitionMode(LampArrayRepetitionMode::Forever); + playlist.EffectStartMode(LampArrayEffectStartMode::Sequential); + + auto indices = GetLampArrayIndices(info->lampArray); + + LampArrayColorRampEffect redRampEffect{ info->lampArray, indices }; + redRampEffect.Color(Colors::Red()); + redRampEffect.RampDuration(ColorRampDuration); + redRampEffect.CompletionBehavior(LampArrayEffectCompletionBehavior::KeepState); + playlist.Append(redRampEffect); + + LampArrayColorRampEffect yellowRampEffect{ info->lampArray, indices }; + yellowRampEffect.Color(Colors::Yellow()); + yellowRampEffect.RampDuration(ColorRampDuration); + yellowRampEffect.CompletionBehavior(LampArrayEffectCompletionBehavior::KeepState); + playlist.Append(yellowRampEffect); + + LampArrayColorRampEffect greenRampEffect{ info->lampArray, indices }; + greenRampEffect.Color(Colors::Green()); + greenRampEffect.RampDuration(ColorRampDuration); + greenRampEffect.CompletionBehavior(LampArrayEffectCompletionBehavior::KeepState); + playlist.Append(greenRampEffect); + + LampArrayColorRampEffect blueRampEffect{ info->lampArray, indices }; + blueRampEffect.Color(Colors::Blue()); + blueRampEffect.RampDuration(ColorRampDuration); + blueRampEffect.CompletionBehavior(LampArrayEffectCompletionBehavior::KeepState); + playlist.Append(blueRampEffect); + + // Add this multi-effect playlist to the list of playlists to start simultaneously. + playlists.push_back(playlist); + } + + // Start the newly created effect playlists simultaneously. + auto playlistsView = winrt::single_threaded_vector(std::move(playlists)).GetView(); + LampArrayEffectPlaylist::StartAll(playlistsView); + + // Remember the playlists so we can stop them later. + m_readyPlaylists = playlistsView; + } +#pragma endregion + + void Scenario2_Effects::PauseButton_Click(IInspectable const&, RoutedEventArgs const&) + { + LampArrayEffectPlaylist::PauseAll(m_readyPlaylists); + } + + void Scenario2_Effects::PlayButton_Click(IInspectable const&, RoutedEventArgs const&) + { + LampArrayEffectPlaylist::StartAll(m_readyPlaylists); + } + + void Scenario2_Effects::StopButton_Click(IInspectable const&, RoutedEventArgs const&) + { + LampArrayEffectPlaylist::StopAll(m_readyPlaylists); + } + + // Helper function that returns all indices of a LampArray in order. + // Used when we want to apply an effect to all Lamps on a LampArray. + std::vector Scenario2_Effects::GetLampArrayIndices(winrt::Windows::Devices::Lights::LampArray const& lampArray) + { + std::vector indices(lampArray.LampCount()); + std::iota(indices.begin(), indices.end(), 0); + return indices; + } +} diff --git a/Samples/LampArray/cppwinrt/Scenario2_Effects.h b/Samples/LampArray/cppwinrt/Scenario2_Effects.h new file mode 100644 index 0000000000..a31d9b0bd0 --- /dev/null +++ b/Samples/LampArray/cppwinrt/Scenario2_Effects.h @@ -0,0 +1,154 @@ +//********************************************************* +// +// 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 "Scenario2_Effects.g.h" + +#include + +namespace winrt::SDKTemplate::implementation +{ + // State for the custom wave effect. + struct CustomEffectState + { + CustomEffectState(winrt::Windows::Devices::Lights::LampArray const& lampArray) : + lampArray(lampArray), + lampNormalizedX(lampArray.LampCount()), + lampColors(lampNormalizedX.size(), winrt::Windows::UI::Colors::Black()) + { } + + winrt::Windows::Devices::Lights::LampArray lampArray{ nullptr }; + uint32_t frameNumber{ 0 }; + + // A vector of each lamp's X position within the bounding box, in the range [0, 1], + // where 0 is the leftmost edge and 1 is the rightmost edge. + std::vector lampNormalizedX; + + // A vector of colors to be applied to each Lamp in the effect callback function. + // The vector is ordered by Lamp index. The ordering does not reflect positions of any Lamp. + std::vector lampColors; + + winrt::Windows::UI::Color color{ winrt::Windows::UI::Colors::HotPink() }; + bool isColorOn{ true }; + }; + + // State for the generated bitmap effect. + struct GeneratedBitmapState + { + uint32_t bitmapWidth; + uint32_t bitmapHeight; + float squareLength; + float squareY; + uint32_t frameNumber{ 0 }; + + winrt::Microsoft::Graphics::Canvas::CanvasDevice canvasDevice{ nullptr }; + winrt::Microsoft::Graphics::Canvas::CanvasRenderTarget offscreenCanvas{ nullptr }; + winrt::Microsoft::Graphics::Canvas::CanvasDrawingSession offscreenDrawingSession{ nullptr }; + + winrt::Windows::Graphics::Imaging::SoftwareBitmap squareBitmap{ nullptr }; + winrt::Windows::Storage::Streams::Buffer squareBuffer{ nullptr }; + }; + + struct Scenario2_Effects : Scenario2_EffectsT + { + public: + Scenario2_Effects(); + + void OnNavigatedTo(winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs const&); + void OnNavigatedFrom(winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs const&); + + winrt::fire_and_forget StaticBitmapButton_Click( + winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + + void FadeButton_Click( + winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + + void CustomEffectButton_Click( + winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + + void GeneratedBitmapButton_Click( + winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + + void CycleButton_Click( + winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + + void PauseButton_Click( + winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + + void PlayButton_Click( + winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + + void StopButton_Click( + winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + + private: + SDKTemplate::MainPage rootPage{ MainPage::Current() }; + + winrt::Windows::Devices::Enumeration::DeviceWatcher m_deviceWatcher{ nullptr }; + + // Currently attached LampArrays + std::vector> m_attachedLampArrays; + + winrt::Windows::Foundation::Collections::IVectorView m_emptyList = + winrt::single_threaded_vector< winrt::Windows::Devices::Lights::Effects::LampArrayEffectPlaylist>().GetView(); + + // Playlists that can be paused, stopped, and started. + winrt::Windows::Foundation::Collections::IVectorView m_readyPlaylists = m_emptyList; + + std::default_random_engine m_random{ std::random_device()() }; + + winrt::Windows::Devices::Enumeration::DeviceWatcher::Added_revoker m_deviceAddedRevoker{}; + winrt::Windows::Devices::Enumeration::DeviceWatcher::Removed_revoker m_deviceRemovedRevoker{}; + + winrt::fire_and_forget Watcher_Added( + winrt::Windows::Devices::Enumeration::DeviceWatcher const&, + winrt::Windows::Devices::Enumeration::DeviceInformation const& deviceInformation); + + winrt::fire_and_forget Watcher_Removed( + winrt::Windows::Devices::Enumeration::DeviceWatcher const&, + winrt::Windows::Devices::Enumeration::DeviceInformationUpdate deviceInformationUpdate); + + void UpdateLampArrayList(); + void CleanupPreviousEffect(); + + static void StaticBitmapEffect_BitmapRequested( + winrt::Windows::Graphics::Imaging::SoftwareBitmap const& bitmap, + winrt::Windows::Devices::Lights::Effects::LampArrayBitmapRequestedEventArgs const& args); + + static void CustomEffect_UpdateRequested( + CustomEffectState* state, + winrt::Windows::Devices::Lights::Effects::LampArrayUpdateRequestedEventArgs const& args); + + static void GeneratedBitmapEffect_UpdateRequested( + GeneratedBitmapState* state, + winrt::Windows::Devices::Lights::Effects::LampArrayBitmapRequestedEventArgs const& args); + + void StartStopAllEffects(bool shouldStart); + + static std::vector GetLampArrayIndices(winrt::Windows::Devices::Lights::LampArray const& lampArray); + }; +} + +namespace winrt::SDKTemplate::factory_implementation +{ + struct Scenario2_Effects : Scenario2_EffectsT + { + }; +} diff --git a/Samples/LampArray/cppwinrt/packages.config b/Samples/LampArray/cppwinrt/packages.config new file mode 100644 index 0000000000..bc89a0ddf6 --- /dev/null +++ b/Samples/LampArray/cppwinrt/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Samples/LampArray/cppwinrt/pch.cpp b/Samples/LampArray/cppwinrt/pch.cpp new file mode 100644 index 0000000000..01484ff5aa --- /dev/null +++ b/Samples/LampArray/cppwinrt/pch.cpp @@ -0,0 +1,6 @@ +// +// pch.cpp +// Include the standard header and generate the precompiled header. +// + +#include "pch.h" diff --git a/Samples/LampArray/cppwinrt/pch.h b/Samples/LampArray/cppwinrt/pch.h new file mode 100644 index 0000000000..360ecb33da --- /dev/null +++ b/Samples/LampArray/cppwinrt/pch.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#define NOMINMAX +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "SampleConfiguration.h" diff --git a/Samples/LampArray/cs/LampArray.csproj b/Samples/LampArray/cs/LampArray.csproj new file mode 100644 index 0000000000..ccd71bd186 --- /dev/null +++ b/Samples/LampArray/cs/LampArray.csproj @@ -0,0 +1,186 @@ + + + + + Debug + x86 + {F43076D1-0EC0-44C0-98B9-A872D58F6977} + AppContainerExe + Properties + SDKTemplate + LampArray + en-US + UAP + 10.0.22621.0 + $(TargetPlatformVersion) + 15 + true + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM + false + prompt + true + + + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM + false + prompt + true + true + + + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x64 + false + prompt + true + + + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x64 + false + prompt + true + true + + + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x86 + false + prompt + true + + + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x86 + false + prompt + true + true + + + + App.xaml.cs + App.xaml + + + MainPage.xaml.cs + MainPage.xaml + + + Properties\AssemblyInfo.cs + + + + + + + + Designer + + + + + App.xaml + MSBuild:Compile + Designer + + + MainPage.xaml + MSBuild:Compile + Designer + + + Scenario1_Basics.xaml + MSBuild:Compile + Designer + + + Scenario2_Effects.xaml + MSBuild:Compile + Designer + + + Styles\Styles.xaml + MSBuild:Compile + Designer + + + + + Properties\Default.rd.xml + + + Assets\microsoft-sdk.png + + + Assets\smallTile-sdk.png + + + Assets\splash-sdk.png + + + Assets\squareTile-sdk.png + + + Assets\storeLogo-sdk.png + + + Assets\tile-sdk.png + + + Assets\windows-sdk.png + + + Assets\grapes.jpg + + + + + 5.0.0 + + + 1.26.0 + + + + 15.0 + + + + \ No newline at end of file diff --git a/Samples/LampArray/cs/LampArray.sln b/Samples/LampArray/cs/LampArray.sln new file mode 100644 index 0000000000..ef76b1f4d7 --- /dev/null +++ b/Samples/LampArray/cs/LampArray.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33723.286 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LampArray", "LampArray.csproj", "{F43076D1-0EC0-44C0-98B9-A872D58F6977}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM = Release|ARM + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Debug|ARM.ActiveCfg = Debug|ARM + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Debug|ARM.Build.0 = Debug|ARM + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Debug|ARM.Deploy.0 = Debug|ARM + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Debug|x64.ActiveCfg = Debug|x64 + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Debug|x64.Build.0 = Debug|x64 + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Debug|x64.Deploy.0 = Debug|x64 + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Debug|x86.ActiveCfg = Debug|x86 + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Debug|x86.Build.0 = Debug|x86 + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Debug|x86.Deploy.0 = Debug|x86 + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Release|ARM.ActiveCfg = Release|ARM + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Release|ARM.Build.0 = Release|ARM + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Release|ARM.Deploy.0 = Release|ARM + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Release|x64.ActiveCfg = Release|x64 + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Release|x64.Build.0 = Release|x64 + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Release|x64.Deploy.0 = Release|x64 + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Release|x86.ActiveCfg = Release|x86 + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Release|x86.Build.0 = Release|x86 + {F43076D1-0EC0-44C0-98B9-A872D58F6977}.Release|x86.Deploy.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {607C33F0-7AF7-4AFB-B496-1ACC2E867D21} + EndGlobalSection +EndGlobal diff --git a/Samples/LampArray/cs/Package.appxmanifest b/Samples/LampArray/cs/Package.appxmanifest new file mode 100644 index 0000000000..5fa6d23940 --- /dev/null +++ b/Samples/LampArray/cs/Package.appxmanifest @@ -0,0 +1,60 @@ + + + + + + + + + + LampArray C# Sample + Microsoft Corporation + Assets\StoreLogo-sdk.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/LampArray/cs/SampleConfiguration.cs b/Samples/LampArray/cs/SampleConfiguration.cs new file mode 100644 index 0000000000..b63694783b --- /dev/null +++ b/Samples/LampArray/cs/SampleConfiguration.cs @@ -0,0 +1,49 @@ +//********************************************************* +// +// 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. +// +//********************************************************* + +using System; +using System.Collections.Generic; +using Windows.Devices.Lights; +using Windows.UI.Xaml.Controls; + +namespace SDKTemplate +{ + public partial class MainPage : Page + { + public const string FEATURE_NAME = "LampArray C# Sample"; + + List scenarios = new List + { + new Scenario() { Title="LampArray Basics", ClassType=typeof(Scenario1_Basics)}, + new Scenario() { Title="LampArray Effects", ClassType=typeof(Scenario2_Effects)}, + }; + } + + public class Scenario + { + public string Title { get; set; } + public Type ClassType { get; set; } + } + + internal class LampArrayInfo + { + public LampArrayInfo(string id, string displayName, LampArray lampArray) + { + this.id = id; + this.displayName = displayName; + this.lampArray = lampArray; + } + + public readonly string id; + public readonly string displayName; + public readonly LampArray lampArray; + } +} diff --git a/Samples/LampArray/cs/Scenario1_Basics.xaml.cs b/Samples/LampArray/cs/Scenario1_Basics.xaml.cs new file mode 100644 index 0000000000..71c2fa5d34 --- /dev/null +++ b/Samples/LampArray/cs/Scenario1_Basics.xaml.cs @@ -0,0 +1,216 @@ +//********************************************************* +// +// 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. +// +//********************************************************* + +using System; +using System.Collections.Generic; +using System.Linq; +using Windows.Devices.Enumeration; +using Windows.Devices.Lights; +using Windows.System; +using Windows.UI; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; + +namespace SDKTemplate +{ + public sealed partial class Scenario1_Basics : Page + { + private MainPage rootPage = MainPage.Current; + + private DeviceWatcher m_deviceWatcher; + + // Currently attached LampArrays + private readonly List m_attachedLampArrays = new List(); + + public Scenario1_Basics() + { + InitializeComponent(); + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + UpdateLampArrayList(); + + // Start watching for newly attached LampArrays. + m_deviceWatcher = DeviceInformation.CreateWatcher(LampArray.GetDeviceSelector()); + + m_deviceWatcher.Added += Watcher_Added; + m_deviceWatcher.Removed += Watcher_Removed; + + m_deviceWatcher.Start(); + } + + protected override void OnNavigatedFrom(NavigationEventArgs e) + { + m_deviceWatcher.Stop(); + } + + private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args) + { + var info = new LampArrayInfo(args.Id, args.Name, await LampArray.FromIdAsync(args.Id)); + + // DeviceWatcher events are invoked on a background thread. + // We need to switch to the foreground thread to update our app UI + // and access member variables without race conditions. + await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + { + if (info.lampArray == null) + { + // A LampArray was found, but Windows couldn't initialize it. + // This suggests a device error. + rootPage.NotifyUser($"Problem with LampArray {info.displayName}.", NotifyType.ErrorMessage); + return; + } + + // Remember this new LampArray and add it to the list. + m_attachedLampArrays.Add(info); + UpdateLampArrayList(); + }); + } + + private async void Watcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args) + { + // DeviceWatcher events are invoked on a background thread. + // We need to switch to the foreground thread to update our app UI + // and access member variables without race conditions. + await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + { + m_attachedLampArrays.RemoveAll(info => info.id == args.Id); + UpdateLampArrayList(); + }); + } + + private void UpdateLampArrayList() + { + string message = $"Attached LampArrays: {m_attachedLampArrays.Count}\n"; + foreach (LampArrayInfo info in m_attachedLampArrays) + { + message += $"\t{info.displayName} ({info.lampArray.LampArrayKind}, {info.lampArray.LampCount} lamps)\n"; + } + LampArraysSummary.Text = message; + } + + private void Apply_Clicked(object sender, RoutedEventArgs e) + { + foreach (LampArrayInfo info in m_attachedLampArrays) + { + ApplySettingsToLampArray(info.lampArray); + } + } + + private void ApplySettingsToLampArray(LampArray lampArray) + { + // Apply the light pattern. + if (OffButton.IsChecked.Value) + { + // Set all Lamps on the LampArray to black, turning them off. + lampArray.SetColor(Colors.Black); + } + else if (SetColorButton.IsChecked.Value) + { + // Set all Lamps on the LampArray to green. + lampArray.SetColor(Colors.Green); + } + else if (GradientButton.IsChecked.Value) + { + SetGradientPatternToLampArray(lampArray); + } + else if (WasdButton.IsChecked.Value) + { + SetWasdPatternToLampArray(lampArray); + } + + // Apply the brightness. Convert the percentage value in the slider to the range [0, 1] expected by BrightnessLevel. + lampArray.BrightnessLevel = BrightnessSlider.Value / BrightnessSlider.Maximum; + } + + private void SetGradientPatternToLampArray(LampArray lampArray) + { + // Create a static gradient on the LampArray from black to red, blending from left to right. + + // For this effect, we want to set the colors of all Lamps. Create an array containing all the Lamp indices + // and an array of Colors. Both arrays must be of the same length. We will use these for SetColorsForIndices later. + int[] indices = Enumerable.Range(0, lampArray.LampCount).ToArray(); + var colors = new Color[lampArray.LampCount]; + + // A LampArray provides information about its size using a three-dimensional logical bounding box, + // as well as the location of each of its Lamps within that bounding box. + // The origin point of the bounding box is the leftmost (X), farthest (Y), uppermost (Z) corner relative to the user. + // LampArray bounding boxes and Lamp positions are measured in meters. + + // Because our gradient will blend from left to right, we will set the Lamp colors according to each Lamp's X position. + // We will make two passes over the LampArray. First, calculate the minimum and maximum X position values of all Lamps. + // This is to "trim" the margins of the bounding box where no Lamps are present. + double maxX = 0; + double minX = lampArray.BoundingBox.X; + + foreach (int i in indices) + { + LampInfo lampInfo = lampArray.GetLampInfo(i); + + if (lampInfo.Position.X > maxX) + { + maxX = lampInfo.Position.X; + } + + if (lampInfo.Position.X < minX) + { + minX = lampInfo.Position.X; + } + } + + // In the second pass, we will calculate the gradient colors for each Lamp. + foreach (int i in indices) + { + LampInfo lampInfo = lampArray.GetLampInfo(i); + + // Calculate the X position for this Lamp relative to the rightmost Lamp. We will scale the Lamp's color by this relative value below. + // (We also want to avoid dividing by zero here in case all Lamps share the same X position, including the single-Lamp case.) + double xProgress = 1.0; + if (maxX != minX) + { + xProgress = (lampInfo.Position.X - minX) / (maxX - minX); + } + + // Apply that value to the expected color for the lamp. + // Maximize the Alpha value to give the color full opacity. + colors[i].A = byte.MaxValue; + + // Set the color's R value based on the Lamp's X position. + // As the X position of the Lamp increases (i.e., is further to the right), + // the Red value of the Lamp's color will also increase. + // This means that the leftmost Lamp(s) will be black (turned off), and the rightmost Lamp(s) will be the brightest red. + colors[i].R = (byte)(xProgress * byte.MaxValue); + } + + // Apply the colors to the LampArray. + lampArray.SetColorsForIndices(colors, indices); + } + + private void SetWasdPatternToLampArray(LampArray lampArray) + { + // Set a background color of blue for the whole LampArray. + lampArray.SetColor(Colors.Blue); + + // Highlight the WASD keys in white, if the LampArray supports addressing its Lamps using a virtual key to Lamp mapping. + // This is typically found on keyboard LampArrays. Other LampArrays will not usually support virtual key based lighting. + if (lampArray.SupportsVirtualKeys) + { + Color[] colors = Enumerable.Repeat(Colors.White, 4).ToArray(); + VirtualKey[] virtualKeys = { VirtualKey.W, VirtualKey.A, VirtualKey.S, VirtualKey.D }; + + lampArray.SetColorsForKeys(colors, virtualKeys); + } + } + } +} diff --git a/Samples/LampArray/cs/Scenario2_Effects.xaml.cs b/Samples/LampArray/cs/Scenario2_Effects.xaml.cs new file mode 100644 index 0000000000..550fbfc110 --- /dev/null +++ b/Samples/LampArray/cs/Scenario2_Effects.xaml.cs @@ -0,0 +1,606 @@ +//********************************************************* +// +// 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. +// +//********************************************************* + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Graphics.Canvas; +using Windows.Devices.Enumeration; +using Windows.Devices.Lights; +using Windows.Devices.Lights.Effects; +using Windows.Graphics.Imaging; +using Windows.Storage; +using Windows.Storage.Streams; +using Windows.UI; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media.Imaging; +using Windows.UI.Xaml.Navigation; + +namespace SDKTemplate +{ + // State for the custom wave effect. + internal class CustomEffectState + { + public LampArray lampArray; + public int frameNumber = 0; + public float[] lampNormalizedX; + public Color[] lampColors; + + public Color color = Colors.HotPink; + public bool isColorOn = true; + } + + // State for the generated bitmap effect. + internal class GeneratedBitmapState + { + // Helper variables for the generated bitmap effect. + public int bitmapWidth; + public int bitmapHeight; + public float squareLength; + public float squareY; + public int frameNumber = 0; + + public CanvasDevice canvasDevice = null; + public CanvasRenderTarget offscreenCanvas = null; + public CanvasDrawingSession offscreenDrawingSession = null; + + public SoftwareBitmap squareBitmap = null; + public Windows.Storage.Streams.Buffer squareBuffer = null; + } + + public sealed partial class Scenario2_Effects : Page + { + private static readonly TimeSpan EffectUpdateInterval = TimeSpan.FromMilliseconds(33); + private static readonly TimeSpan ColorRampDuration = TimeSpan.FromMilliseconds(500); + private static readonly int CustomEffectFrameCount = 30; + private static readonly int GeneratedBitmapEffectFrameCount = 60; + + private DeviceWatcher m_deviceWatcher; + + // Currently attached LampArrays + private readonly List m_attachedLampArrays = new List(); + + private readonly IReadOnlyList m_emptyList = new List(); + // Playlists that can be paused, stopped, and started. + private IReadOnlyList m_readyPlaylists; + + private Random m_random = new Random(); + + private MainPage rootPage = MainPage.Current; + + public Scenario2_Effects() + { + InitializeComponent(); + m_readyPlaylists = m_emptyList; + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + UpdateLampArrayList(); + + // Start watching for newly attached LampArrays. + m_deviceWatcher = DeviceInformation.CreateWatcher(LampArray.GetDeviceSelector()); + + m_deviceWatcher.Added += Watcher_Added; + m_deviceWatcher.Removed += Watcher_Removed; + + m_deviceWatcher.Start(); + } + + protected override void OnNavigatedFrom(NavigationEventArgs e) + { + m_deviceWatcher.Stop(); + + // When changing scenarios, stop our effects to prevent them from interfering with the new scenario. + CleanupPreviousEffect(); + } + + private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args) + { + var info = new LampArrayInfo(args.Id, args.Name, await LampArray.FromIdAsync(args.Id)); + + // DeviceWatcher events are invoked on a background thread. + // We need to switch to the foreground thread to update our app UI + // and access member variables without race conditions. + await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + { + if (info.lampArray == null) + { + // A LampArray was found, but Windows couldn't initialize it. + // This suggests a device error. + rootPage.NotifyUser($"Problem with LampArray {info.displayName}.", NotifyType.ErrorMessage); + return; + } + + // Initial condition for the new LampArray is all lights off. + info.lampArray.SetColor(Colors.Black); + + m_attachedLampArrays.Add(info); + + UpdateLampArrayList(); + }); + } + + private async void Watcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args) + { + // DeviceWatcher events are invoked on a background thread. + // We need to switch to the foreground thread to update our app UI + // and access member variables without race conditions. + await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + { + m_attachedLampArrays.RemoveAll(info => info.id == args.Id); + UpdateLampArrayList(); + }); + } + + private void UpdateLampArrayList() + { + string message = $"Attached LampArrays: {m_attachedLampArrays.Count}\n"; + foreach (LampArrayInfo info in m_attachedLampArrays) + { + message += $"\t{info.displayName} ({info.lampArray.LampArrayKind.ToString()}, {info.lampArray.LampCount} lamps)\n"; + } + LampArraysSummary.Text = message; + } + + // This method will be called when a new effect is selected. It will stop any running + // LampArrayEffectPlaylists in progress and reset LampArray state in preparation for the new effect. + private void CleanupPreviousEffect() + { + // Clear the bitmap image in the app UI, as it is no longer current. + ImageBitmap.Source = null; + + // Passing all the playlists to a single call to StopAll will stop them + // simultaneously across all attached LampArrays. + LampArrayEffectPlaylist.StopAll(m_readyPlaylists); + m_readyPlaylists = m_emptyList; + } + + #region Static bitmap effect + private async void StaticBitmapButton_Click(object sender, RoutedEventArgs e) + { + CleanupPreviousEffect(); + + // This effect will display a specified bitmap image on all connected LampArrays. + // The image will be stretched and scaled to fit the Lamp dimensions of each LampArray. + + // First, open the desired bitmap file and convert it to a SoftwareBitmap + Uri bitmapUri = new Uri("ms-appx:///Assets/grapes.jpg"); + StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(bitmapUri); + SoftwareBitmap bitmap; + using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read)) + { + BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream); + bitmap = await decoder.GetSoftwareBitmapAsync(); + } + + // Show our bitmap image in the app UI + ImageBitmap.Source = new BitmapImage(bitmapUri); + + // Gather the new LampArrayEffectPlaylists in this collection so we can start them simultaneously. + var playlists = new List(); + + // Create the effect playlist for each LampArray and add it to the collection. + foreach (LampArrayInfo info in m_attachedLampArrays) + { + var bitmapEffect = new LampArrayBitmapEffect(info.lampArray, GetLampArrayIndices(info.lampArray)) + { + // We want to run this effect until is stopped. For the purposes of this sample, + // set the effect Duration to run as long as possible. + Duration = TimeSpan.MaxValue, + + // The UpdateInterval specifies how frequently we want to receive our callback function. + // Since this effect will display a static image, we only need to update once per effect iteration. + // Set this interval to be the same as the effect duration. + UpdateInterval = TimeSpan.MaxValue + }; + + // Set the event handler for our effect. Use a helper function to + // to specify a bitmap to display on the device. + bitmapEffect.BitmapRequested += (_, args) => StaticBitmapEffect_BitmapRequested(bitmap, args); + + // Create a playlist consisting of our static bitmap effect. + var playlist = new LampArrayEffectPlaylist(); + playlist.Append(bitmapEffect); + + // Add it to the list of playlists to start simultaneously. + playlists.Add(playlist); + } + + // Start the newly created effect playlists simultaneously. + LampArrayEffectPlaylist.StartAll(playlists); + + // Remember the playlists so we can stop them later. + m_readyPlaylists = playlists; + } + + // Helper function for BitmapRequested event handler that + // specifies the bitmap to display on the device. + // The frequency of the event is customizable. + // As configured above when setting up the static bitmap effect, this event will + // be raised only once per effect iteration, since we set the update interval + // equal to the effect duration. + private static void StaticBitmapEffect_BitmapRequested(SoftwareBitmap bitmap, LampArrayBitmapRequestedEventArgs args) + { + // In a BitmapRequested handler, we can change the bitmap here to produce animations. + // For the static bitmap effect, show the same image each time. + args.UpdateBitmap(bitmap); + } + #endregion + + #region Fade effect + private void FadeButton_Click(object sender, RoutedEventArgs e) + { + CleanupPreviousEffect(); + + // This effect will set each Lamp to a random color and blink all Lamps simultaneously. + // LampArrayBlinkEffect only supports a single color at a time. To create the desired effect with + // different colors for each Lamp, we will create a separate LampArrayBlinkEffect for each Lamp, + // specifying that we only want the effect to apply to a single Lamp index. + // We will then add each Lamp effect to the playlist for its corresponding LampArray and configure the + // playlist to run its effects simultaneously instead of sequentially. + + // Gather the new LampArrayEffectPlaylists in this collection so we can start them simultaneously. + var playlists = new List(); + + // Create the effect playlist for each LampArray and add it to the collection. + foreach (LampArrayInfo context in m_attachedLampArrays) + { + var playlist = new LampArrayEffectPlaylist(); + + // Configure the playlist to run the effects simultaneously. + // This will result in all Lamps for this LampArray blinking in unison, + // even though their colors will be different. + playlist.EffectStartMode = LampArrayEffectStartMode.Simultaneous; + + for (int i = 0; i < context.lampArray.LampCount; i++) + { + // Generate a color for each Lamp by choosing a random Red/Green/Blue combination. + var color = new Color + { + A = byte.MaxValue, // Maximum alpha makes it opaque + R = (byte)(m_random.Next(byte.MaxValue + 1)), + G = (byte)(m_random.Next(byte.MaxValue + 1)), + B = (byte)(m_random.Next(byte.MaxValue + 1)), + }; + + // Create the effect and apply it to a single Lamp. + playlist.Append(new LampArrayBlinkEffect(context.lampArray, new[] { i }) + { + Color = color, + + // We can adjust the timing of the LampArrayBlinkEffect using these properties. + // Specify how long it should take for the full color to fade in from off/Black to peak intensity. + AttackDuration = TimeSpan.FromMilliseconds(300), + + // Specify how long to display the color at peak intensity. + SustainDuration = TimeSpan.FromMilliseconds(500), + + // Specify how long it should take for the full color to fade out from peak intensity to off/Black. + DecayDuration = TimeSpan.FromMilliseconds(800), + + // Specify how long the Lamps should stay dark between blink repetitions. + RepetitionDelay = TimeSpan.FromMilliseconds(100), + + // Repeat the blink sequence indefinitely until the effect playlist is stopped. + RepetitionMode = LampArrayRepetitionMode.Forever + }); + } + + // Add it to the list of playlists to start simultaneously. + playlists.Add(playlist); + } + + // Start the newly created effect playlists simultaneously. + LampArrayEffectPlaylist.StartAll(playlists); + + // Remember the playlists so we can stop them later. + m_readyPlaylists = playlists; + } + #endregion + + #region Custom effect + // Windows.Devices.Lights.Effects contains several built-in effect types. The LampArrayCustomEffect is provided for + // greater customization of lighting beyond these simple effects. It enables the SetColor controls of LampArray to + // be integrated and scheduled with other effects and effect playlists. + // This sample will demonstrate using LampArrayCustomEffect to display a simple lighting animation. + private void CustomEffectButton_Click(object sender, RoutedEventArgs e) + { + CleanupPreviousEffect(); + + // This effect will use LampArrayCustomEffect to create a wave-like animation. + // Lamps will turn on from left to right across the LampArray, then turn off in a similar manner. + + // Gather the new LampArrayEffectPlaylists in this collection so we can start them simultaneously. + var playlists = new List(); + + // Create the effect playlist for each LampArray and add it to the collection. + foreach (LampArrayInfo info in m_attachedLampArrays) + { + LampArray lampArray = info.lampArray; + int[] indices = GetLampArrayIndices(lampArray); + float boundingBoxWidth = lampArray.BoundingBox.X; + // Avoid division by zero when calculating normalized positions. + if (boundingBoxWidth == 0.0f) + { + boundingBoxWidth = 1.0f; + } + CustomEffectState state = new CustomEffectState() + { + lampArray = lampArray, + lampNormalizedX = new float[lampArray.LampCount], + lampColors = new Color[lampArray.LampCount] + }; + + // Calculate each lamp's normalized X position relative to the width of the bounding box. + // (zero will be the leftmost edge of the bounding box, and 1 will be the rightmost edge). + for (int i = 0; i < state.lampNormalizedX.Length; i++) + { + LampInfo lampInfo = lampArray.GetLampInfo(i); + state.lampNormalizedX[i] = lampInfo.Position.X / boundingBoxWidth; + } + + var customEffect = new LampArrayCustomEffect(lampArray, indices) + { + // We want to run this effect until is stopped. For the purposes of this sample, + // set the effect Duration to run as long as possible. + Duration = TimeSpan.MaxValue, + + // The UpdateInterval specifies how frequently we want to receive our callback function. + // For our animation, set this interval to 33 milliseconds, which equates to approximately 30 frames per second. + UpdateInterval = EffectUpdateInterval + }; + + // This list will track the colors to be applied to each Lamp in the effect callback function. + // The list is ordered by Lamp index. The ordering does not reflect positions of any Lamp. + state.lampColors = Enumerable.Repeat(Colors.Black, state.lampArray.LampCount).ToArray(); + + // This event handler will be called on every frame to update the animation. + customEffect.UpdateRequested += (_, args) => CustomEffect_UpdateRequested(state, args); + + // Create a playlist consisting of our wave effect. + var playlist = new LampArrayEffectPlaylist(); + playlist.Append(customEffect); + + // Add it to the list of playlists to start simultaneously. + playlists.Add(playlist); + } + + // Start the newly created effect playlists. + LampArrayEffectPlaylist.StartAll(playlists); + + // Remember the playlists so we can stop them later. + m_readyPlaylists = playlists; + } + + // Helper for the LampArrayCustomEffect.UpdateRequested event, called periodically at the requested UpdateInterval. + // This enables more complex lighting patterns, such as animations. It also supports integration and scheduling with + // other effects and effect playlists. + // As configured above, this callback will be invoked every 33 milliseconds, or about 30 frames per second. + private static void CustomEffect_UpdateRequested(CustomEffectState state, LampArrayUpdateRequestedEventArgs args) + { + // Update the Lamp colors for the current frame. Start by calculating how far into the current iteration we are. + // We will use this to mark how far across the LampArray bounding box the effect has reached. + double effectProgress = (state.frameNumber + 1) / (double)CustomEffectFrameCount; + + // If Lamps were being turned off in the previous iteration, turn them on now, and vice versa. + Color color = state.isColorOn ? state.color : Colors.Black; + + for (int i = 0; i < state.lampNormalizedX.Length; i++) + { + // All Lamps that are to the left of our marker (normalized to the LampArray bounding box width) + // should have their states updated. + if (state.lampNormalizedX[i] <= effectProgress) + { + state.lampColors[i] = color; + } + } + + // Apply the Lamp states to the LampArray. + args.SetColorsForIndices(state.lampColors, GetLampArrayIndices(state.lampArray)); + + // Increment our frame counter, with wraparound if we've reached the frame limit. + // Also update whether the lamps should be turned on or off for the next iteration. + if (++state.frameNumber == CustomEffectFrameCount) + { + state.frameNumber = 0; + state.isColorOn = !state.isColorOn; + } + } + #endregion + + #region Generated bitmap effect + // This effect uses Win2D to create a simple animation. On each frame, we + // generate a bitmap image and apply it to the entire LampArray. + // The effect illustrates dynamically creating a bitmap image and using it in a lighting effect. + // LampArrayBitmapEffect could be used for in-game lighting and/or integration with 2D graphics libraries. + private void GeneratedBitmapButton_Click(object sender, RoutedEventArgs e) + { + CleanupPreviousEffect(); + + // Gather the new LampArrayEffectPlaylists in this collection so we can start them simultaneously. + var playlists = new List(); + + // Create the effect playlist for each LampArray and add it to the collection. + foreach (LampArrayInfo info in m_attachedLampArrays) + { + GeneratedBitmapState state = new GeneratedBitmapState(); + + var generatedBitmapEffect = new LampArrayBitmapEffect(info.lampArray, GetLampArrayIndices(info.lampArray)) + { + // We want to run this effect until is stopped. For the purposes of this sample, + // set the effect Duration to run as long as possible. + Duration = TimeSpan.MaxValue, + + // The UpdateInterval specifies how frequently we want to receive our callback function. + // For our animation, set this interval to 33 milliseconds, which equates to approximately 30 frames per second. + UpdateInterval = EffectUpdateInterval + }; + + state.bitmapWidth = (int)generatedBitmapEffect.SuggestedBitmapSize.Width; + state.bitmapHeight = (int)generatedBitmapEffect.SuggestedBitmapSize.Height; + + // The size of the square is half the height or half the width, whichever is smaller. + state.squareLength = Math.Min(state.bitmapHeight, state.bitmapWidth) / 2.0f; + + // Vertically center the square. + state.squareY = (state.bitmapHeight - state.squareLength) / 2.0f; + + // Setup our Win2D helper objects. + state.canvasDevice = CanvasDevice.GetSharedDevice(); + + state.offscreenCanvas = new CanvasRenderTarget(state.canvasDevice, state.bitmapWidth, state.bitmapHeight, 96); + + state.offscreenDrawingSession = state.offscreenCanvas.CreateDrawingSession(); + + // The generated bitmap encompasses the entire LampArray + state.squareBitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8, state.bitmapWidth, state.bitmapHeight, BitmapAlphaMode.Premultiplied); + + state.squareBuffer = new Windows.Storage.Streams.Buffer((uint)(state.bitmapWidth * state.bitmapHeight * 4)); + + // This event handler will be called on every frame to update the bitmap to display on the device. + generatedBitmapEffect.BitmapRequested += (_, args) => GeneratedBitmapEffect_UpdateRequested(state, args); + + // Create a playlist consisting of our bitmap effect. + var playlist = new LampArrayEffectPlaylist(); + playlist.Append(generatedBitmapEffect); + + // Add it to the list of playlists to start simultaneously. + playlists.Add(playlist); + } + + // Start the newly created effect playlists. + LampArrayEffectPlaylist.StartAll(playlists); + + // Remember the playlists so we can stop them later. + m_readyPlaylists = playlists; + } + + // Callback function that allows us to specify a bitmap to display on the device. + // The frequency of the effect callback is customizable. + // As configured above when setting up the generated bitmap effect, this will be invoked + // every 33 milliseconds, or approximately 30 frames per second. + // To demonstrate using a LampArrayBitmapEffect to perform lighting animations, + // this callback will create an animation of a red rectangle moving left to right + // across a blue background. + private static void GeneratedBitmapEffect_UpdateRequested(GeneratedBitmapState state, LampArrayBitmapRequestedEventArgs args) + { + // Draw a red rectangle on a blue background. + state.offscreenDrawingSession.Clear(Colors.Blue); + + // The rectangle starts out just beyond the left edge of the keyboard, + // then moves to the right until it just leaves the keyboard, + // and then repeats. + float squareX = (state.frameNumber * (state.bitmapWidth + state.squareLength) / GeneratedBitmapEffectFrameCount) - state.squareLength; + state.offscreenDrawingSession.FillRectangle(squareX, state.squareY, state.squareLength, state.squareLength, Colors.Red); + + state.offscreenDrawingSession.Flush(); + + // Apply the generated bitmap to the LampArray. + state.offscreenCanvas.GetPixelBytes(state.squareBuffer); + state.squareBitmap.CopyFromBuffer(state.squareBuffer); + + args.UpdateBitmap(state.squareBitmap); + + // Advance to the next frame for the next iteration. + if (++state.frameNumber == GeneratedBitmapEffectFrameCount) + { + state.frameNumber = 0; + } + } + #endregion + + #region Color cycle effect + private void CycleButton_Click(object sender, RoutedEventArgs e) + { + CleanupPreviousEffect(); + + // This effect uses LampArrayColorRampEffect to perform a rainbow cycle pattern on all Lamps. + // We do this for each LampArray by creating a LampArrayEffectPlaylist containing four LampArrayColorRampEffects. + // Each effect will blend from the previously set color to a new color over a 500ms interval. + + // Gather the new LampArrayEffectPlaylists in this collection so we can start them simultaneously. + var playlists = new List(); + + // Create the effect playlist for each LampArray and add it to the collection. + foreach (LampArrayInfo info in m_attachedLampArrays) + { + var playlist = new LampArrayEffectPlaylist() + { + EffectStartMode = LampArrayEffectStartMode.Sequential, + RepetitionMode = LampArrayRepetitionMode.Forever, + }; + + playlist.Append(new LampArrayColorRampEffect(info.lampArray, GetLampArrayIndices(info.lampArray)) + { + Color = Colors.Red, + RampDuration = ColorRampDuration, + CompletionBehavior = LampArrayEffectCompletionBehavior.KeepState + }); + + playlist.Append(new LampArrayColorRampEffect(info.lampArray, GetLampArrayIndices(info.lampArray)) + { + Color = Colors.Yellow, + RampDuration = ColorRampDuration, + CompletionBehavior = LampArrayEffectCompletionBehavior.KeepState + }); + + playlist.Append(new LampArrayColorRampEffect(info.lampArray, GetLampArrayIndices(info.lampArray)) + { + Color = Colors.Green, + RampDuration = ColorRampDuration, + CompletionBehavior = LampArrayEffectCompletionBehavior.KeepState + }); + + playlist.Append(new LampArrayColorRampEffect(info.lampArray, GetLampArrayIndices(info.lampArray)) + { + Color = Colors.Blue, + RampDuration = ColorRampDuration, + CompletionBehavior = LampArrayEffectCompletionBehavior.KeepState + }); + + // Add it to the list of playlists to start simultaneously. + playlists.Add(playlist); + } + + // Start the newly created effect playlists. + LampArrayEffectPlaylist.StartAll(playlists); + + // Remember the playlists so we can stop them later. + m_readyPlaylists = playlists; + } + #endregion + + private void PauseButton_Click(object sender, RoutedEventArgs e) + { + LampArrayEffectPlaylist.PauseAll(m_readyPlaylists); + } + + private void PlayButton_Click(object sender, RoutedEventArgs e) + { + LampArrayEffectPlaylist.StartAll(m_readyPlaylists); + } + + private void StopButton_Click(object sender, RoutedEventArgs e) + { + LampArrayEffectPlaylist.StopAll(m_readyPlaylists); + } + + // Helper function that returns all indices of a LampArray in order. + // Used when we want to apply an effect to all Lamps on a LampArray. + private static int[] GetLampArrayIndices(LampArray lampArray) + { + return Enumerable.Range(0, lampArray.LampCount).ToArray(); + } + } +} diff --git a/Samples/LampArray/shared/Scenario1_Basics.xaml b/Samples/LampArray/shared/Scenario1_Basics.xaml new file mode 100644 index 0000000000..f037cc77e5 --- /dev/null +++ b/Samples/LampArray/shared/Scenario1_Basics.xaml @@ -0,0 +1,60 @@ + + + + + + + + + Demonstrates basic device properties and RGB lighting operations of Windows.Devices.Lights.LampArray. + + + + Off + All green + Gradient + Highlight WASD + + + + + + + + + + + + + + + + + + + + + + + + + + + + Attached LampArrays + + + + + + + diff --git a/Samples/MobileHotspot/README.md b/Samples/MobileHotspot/README.md new file mode 100644 index 0000000000..b52c753406 --- /dev/null +++ b/Samples/MobileHotspot/README.md @@ -0,0 +1,62 @@ +--- +page_type: sample +languages: +- csharp +- cppwinrt +products: +- windows +- windows-uwp +urlFragment: MobileHotspot +extendedZipContent: +- path: SharedContent + target: SharedContent +- path: LICENSE + target: LICENSE +description: "Shows how to use the Windows.Networking.NetworkOperators.NetworkOperatorTetheringAccessPointConfiguration APIs." +--- + +# Mobile hotspot sample + +Shows how to use the [Windows.Networking.NetworkOperators.NetworkOperatorTetheringAccessPointConfiguration](https://docs.microsoft.com/en-us/uwp/api/Windows.Networking.NetworkOperators.NetworkOperatorTetheringAccessPointConfiguration) APIs. + +> **Note:** This sample is part of a large collection of UWP feature samples. +> You can download this sample as a standalone ZIP file +> [from docs.microsoft.com](https://docs.microsoft.com/samples/microsoft/windows-universal-samples/mobilehotspot/), +> or you can download the entire collection as a single +> [ZIP file](https://github.com/Microsoft/Windows-universal-samples/archive/master.zip), but be +> sure to unzip everything to access shared dependencies. For more info on working with the ZIP file, +> the samples collection, and GitHub, see [Get the UWP samples from GitHub](https://aka.ms/ovu2uq). +> For more samples, see the [Samples portal](https://aka.ms/winsamples) on the Windows Dev Center. + +Specifically, this sample demonstrates: + +- Reading the current Mobile hotspot configuration +- Editing and applying a new configuration ([Windows.Networking.NetworkOperators.NetworkOperatorTetheringAccessPointConfiguration](https://docs.microsoft.com/uwp/api/Windows.Networking.NetworkOperators.NetworkOperatorTetheringAccessPointConfiguration)) +- Turning the mobile hotspot on and off + +## System requirements + +**Client:** Windows 10, Build 19041 and later. + +**Server:** N/A + +## Build the sample + +1. If you download the samples ZIP, be sure to unzip the entire archive, not just the folder with the sample you want to build. +2. Start Microsoft Visual Studio and select **File** \> **Open** \> **Project/Solution**. +3. Starting in the folder where you unzipped the samples, go to the Samples subfolder, then the subfolder for this specific sample, then the subfolder for your preferred language (C++, C#, or JavaScript). Double-click the Visual Studio Solution (.sln) file. +4. Press Ctrl+Shift+B, or select **Build** \> **Build Solution**. + +## Run the sample + +The next steps depend on whether you just want to deploy the sample or you want to both deploy and run it. + +### Deploying the sample + +- Select **Build** \> **Deploy Solution**. + +### Deploying and running the sample + +- To debug the sample and then run it, press F5 or select **Debug** \> **Start Debugging**. To run the sample without debugging, press Ctrl+F5 or select **Debug** \> **Start Without Debugging**. + +Each scenario has buttons you can use to perform the actions described in the scenario text. If you do not have any WiFi Adapters on your system and an active Internet connection to share, you will not be able to experience the scenarios. diff --git a/Samples/MobileHotspot/cppwinrt/MobileHotspot.sln b/Samples/MobileHotspot/cppwinrt/MobileHotspot.sln new file mode 100644 index 0000000000..f345c8fc37 --- /dev/null +++ b/Samples/MobileHotspot/cppwinrt/MobileHotspot.sln @@ -0,0 +1,51 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MobileHotspot", "MobileHotspot.vcxproj", "{A2D33437-5635-4832-AD71-08D653A37053}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A2D33437-5635-4832-AD71-08D653A37053}.Debug|ARM.ActiveCfg = Debug|ARM + {A2D33437-5635-4832-AD71-08D653A37053}.Debug|ARM.Build.0 = Debug|ARM + {A2D33437-5635-4832-AD71-08D653A37053}.Debug|ARM.Deploy.0 = Debug|ARM + {A2D33437-5635-4832-AD71-08D653A37053}.Debug|ARM64.ActiveCfg = Debug|x64 + {A2D33437-5635-4832-AD71-08D653A37053}.Debug|ARM64.Build.0 = Debug|x64 + {A2D33437-5635-4832-AD71-08D653A37053}.Debug|ARM64.Deploy.0 = Debug|x64 + {A2D33437-5635-4832-AD71-08D653A37053}.Debug|x64.ActiveCfg = Debug|x64 + {A2D33437-5635-4832-AD71-08D653A37053}.Debug|x64.Build.0 = Debug|x64 + {A2D33437-5635-4832-AD71-08D653A37053}.Debug|x64.Deploy.0 = Debug|x64 + {A2D33437-5635-4832-AD71-08D653A37053}.Debug|x86.ActiveCfg = Debug|Win32 + {A2D33437-5635-4832-AD71-08D653A37053}.Debug|x86.Build.0 = Debug|Win32 + {A2D33437-5635-4832-AD71-08D653A37053}.Debug|x86.Deploy.0 = Debug|Win32 + {A2D33437-5635-4832-AD71-08D653A37053}.Release|ARM.ActiveCfg = Release|ARM + {A2D33437-5635-4832-AD71-08D653A37053}.Release|ARM.Build.0 = Release|ARM + {A2D33437-5635-4832-AD71-08D653A37053}.Release|ARM.Deploy.0 = Release|ARM + {A2D33437-5635-4832-AD71-08D653A37053}.Release|ARM64.ActiveCfg = Release|ARM + {A2D33437-5635-4832-AD71-08D653A37053}.Release|ARM64.Build.0 = Release|ARM + {A2D33437-5635-4832-AD71-08D653A37053}.Release|ARM64.Deploy.0 = Release|ARM + {A2D33437-5635-4832-AD71-08D653A37053}.Release|x64.ActiveCfg = Release|x64 + {A2D33437-5635-4832-AD71-08D653A37053}.Release|x64.Build.0 = Release|x64 + {A2D33437-5635-4832-AD71-08D653A37053}.Release|x64.Deploy.0 = Release|x64 + {A2D33437-5635-4832-AD71-08D653A37053}.Release|x86.ActiveCfg = Release|Win32 + {A2D33437-5635-4832-AD71-08D653A37053}.Release|x86.Build.0 = Release|Win32 + {A2D33437-5635-4832-AD71-08D653A37053}.Release|x86.Deploy.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {188076BF-8AB0-4B34-82D3-BCDA00EFE46F} + EndGlobalSection +EndGlobal diff --git a/Samples/MobileHotspot/cppwinrt/MobileHotspot.vcxproj b/Samples/MobileHotspot/cppwinrt/MobileHotspot.vcxproj new file mode 100644 index 0000000000..23bd173e66 --- /dev/null +++ b/Samples/MobileHotspot/cppwinrt/MobileHotspot.vcxproj @@ -0,0 +1,191 @@ + + + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), LICENSE))\SharedContent + + + true + true + {A2D33437-5635-4832-AD71-08D653A37053} + MobileHotspot + SDKTemplate + en-US + 15.0 + true + Windows Store + 10.0 + 10.0.22621.0 + $(WindowsTargetPlatformVersion) + + + + + Debug + ARM + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + Win32 + + + Release + x64 + + + + Application + Unicode + + + true + true + + + false + true + false + + + + + + + + $(VC_IncludePath);$(UniversalCRT_IncludePath);$(WindowsSDK_IncludePath);$(SharedContentDir)\cppwinrt + true + + + + Use + pch.h + $(IntDir)pch.pch + Level4 + %(AdditionalOptions) /bigobj /std:c++17 + 4453;28204 + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + NDEBUG;%(PreprocessorDefinitions) + + + + + $(SharedContentDir)\xaml\App.xaml + + + $(SharedContentDir)\xaml\MainPage.xaml + + + + ..\shared\Scenario1_ConfigureMobileHotspot.xaml + + + ..\shared\Scenario2_ToggleMobileHotspot.xaml + + + + + + Designer + + + Designer + + + + + Styles\Styles.xaml + + + + + $(SharedContentDir)\xaml\App.xaml + + + $(SharedContentDir)\xaml\MainPage.xaml + + + SampleConfiguration.h + + + ..\shared\Scenario1_ConfigureMobileHotspot.xaml + + + ..\shared\Scenario2_ToggleMobileHotspot.xaml + + + Create + pch.h + + + Project.idl + + + + + $(SharedContentDir)\xaml\MainPage.xaml + + + + + + Designer + + + + + Assets\microsoft-sdk.png + + + Assets\smallTile-sdk.png + + + Assets\splash-sdk.png + + + Assets\squareTile-sdk.png + + + Assets\storeLogo-sdk.png + + + Assets\tile-sdk.png + + + Assets\windows-sdk.png + + + + + + + + + + + + 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/MobileHotspot/cppwinrt/MobileHotspot.vcxproj.filters b/Samples/MobileHotspot/cppwinrt/MobileHotspot.vcxproj.filters new file mode 100644 index 0000000000..772e0247e4 --- /dev/null +++ b/Samples/MobileHotspot/cppwinrt/MobileHotspot.vcxproj.filters @@ -0,0 +1,59 @@ + + + + + 4416d50a-7676-4d0a-9b2c-91ff70c6047f + bmp;fbx;gif;jpg;jpeg;tga;tiff;tif;png + + + + + + + + + + + + + + + + + + + + + + + + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + + + + + + + \ No newline at end of file diff --git a/Samples/MobileHotspot/cppwinrt/Package.appxmanifest b/Samples/MobileHotspot/cppwinrt/Package.appxmanifest new file mode 100644 index 0000000000..5434514e7d --- /dev/null +++ b/Samples/MobileHotspot/cppwinrt/Package.appxmanifest @@ -0,0 +1,46 @@ + + + + + + + MobileHotspot C++/WinRT Sample + Microsoft Corporation + Assets\storelogo-sdk.png + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/MobileHotspot/cppwinrt/Project.idl b/Samples/MobileHotspot/cppwinrt/Project.idl new file mode 100644 index 0000000000..f15083ba68 --- /dev/null +++ b/Samples/MobileHotspot/cppwinrt/Project.idl @@ -0,0 +1,25 @@ +//********************************************************* +// +// 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. +// +//********************************************************* + +namespace SDKTemplate +{ + [default_interface] + runtimeclass Scenario1_ConfigureMobileHotspot : Windows.UI.Xaml.Controls.Page + { + Scenario1_ConfigureMobileHotspot(); + } + + [default_interface] + runtimeclass Scenario2_ToggleMobileHotspot : Windows.UI.Xaml.Controls.Page + { + Scenario2_ToggleMobileHotspot(); + } +} diff --git a/Samples/MobileHotspot/cppwinrt/SampleConfiguration.cpp b/Samples/MobileHotspot/cppwinrt/SampleConfiguration.cpp new file mode 100644 index 0000000000..eb7f1ddc95 --- /dev/null +++ b/Samples/MobileHotspot/cppwinrt/SampleConfiguration.cpp @@ -0,0 +1,133 @@ +//********************************************************* +// +// 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 "MainPage.h" +#include "SampleConfiguration.h" + +using namespace winrt; +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::Networking::Connectivity; +using namespace Windows::Networking::NetworkOperators; + +namespace winrt::SDKTemplate +{ + hstring implementation::MainPage::FEATURE_NAME() + { + return L"MobileHotspot C++/WinRT Sample"; + } + + IVector implementation::MainPage::scenariosInner = winrt::single_threaded_observable_vector( + { + Scenario{ L"Configure Mobile Hotspot", xaml_typename() }, + Scenario{ L"Toggle Mobile Hotspot", xaml_typename() }, + }); + + + NetworkOperatorTetheringManager TryGetCurrentNetworkOperatorTetheringManager() + { + // Get the connection profile associated with the internet connection currently used by the local machine. + ConnectionProfile currentConnectionProfile = NetworkInformation::GetInternetConnectionProfile(); + if (currentConnectionProfile == nullptr) + { + MainPage::Current().NotifyUser(L"System is not connected to the Internet.", NotifyType::ErrorMessage); + return nullptr; + } + + TetheringCapability tetheringCapability = + NetworkOperatorTetheringManager::GetTetheringCapabilityFromConnectionProfile(currentConnectionProfile); + + if (tetheringCapability != TetheringCapability::Enabled) + { + hstring message; + switch (tetheringCapability) + { + case TetheringCapability::DisabledByGroupPolicy: + message = L"Tethering is disabled due to group policy."; + break; + case TetheringCapability::DisabledByHardwareLimitation: + message = L"Tethering is not available due to hardware limitations."; + break; + case TetheringCapability::DisabledByOperator: + message = L"Tethering operations are disabled for this account by the network operator."; + break; + case TetheringCapability::DisabledByRequiredAppNotInstalled: + message = L"An application required for tethering operations is not available."; + break; + case TetheringCapability::DisabledBySku: + message = L"Tethering is not supported by the current account services."; + break; + case TetheringCapability::DisabledBySystemCapability: + // This will occur if the "wiFiControl" capability is missing from the App. + message = L"This app is not configured to access Wi-Fi devices on this machine."; + break; + default: + message = L"Tethering is disabled on this machine. (Code " + to_hstring(static_cast(tetheringCapability)) + L")."; + break; + } + MainPage::Current().NotifyUser(message, NotifyType::ErrorMessage); + return nullptr; + } + + try + { + return NetworkOperatorTetheringManager::CreateFromConnectionProfile(currentConnectionProfile); + } + catch (hresult_error const& ex) + { + if (ex.code() == HRESULT_FROM_WIN32(ERROR_NOT_FOUND)) + { + MainPage::Current().NotifyUser(L"System has no Wi-Fi adapters.", NotifyType::ErrorMessage); + return nullptr; + } + throw; + } + } + + hstring GetFriendlyName(TetheringWiFiBand value) + { + switch (value) + { + case TetheringWiFiBand::Auto: return L"Any available"; + case TetheringWiFiBand::TwoPointFourGigahertz: return L"2.4 GHz"; + case TetheringWiFiBand::FiveGigahertz: return L"5 Ghz"; + default: return L"Unknown (" + to_hstring(static_cast(value)) + L")"; + } + } + + // Calls NetworkOperatorTetheringAccessPointConfiguration.IsBandSupported but handles + // certain boundary cases. + + bool IsBandSupported(NetworkOperatorTetheringAccessPointConfiguration const& configuration, TetheringWiFiBand band) + { + // "Auto" mode is always supported even though it is technically not a frequency band. + if (band == TetheringWiFiBand::Auto) + { + return true; + } + + try + { + return configuration.IsBandSupported(band); + } + catch (hresult_error const& ex) + { + if (ex.code() == HRESULT_FROM_WIN32(ERROR_INVALID_STATE)) + { + // The WiFi device has been disconnected. Report that we support nothing. + return false; + } + throw; + } + } +} diff --git a/Samples/MobileHotspot/cppwinrt/SampleConfiguration.h b/Samples/MobileHotspot/cppwinrt/SampleConfiguration.h new file mode 100644 index 0000000000..141e7128bf --- /dev/null +++ b/Samples/MobileHotspot/cppwinrt/SampleConfiguration.h @@ -0,0 +1,20 @@ +//********************************************************* +// +// 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 "pch.h" + +namespace winrt::SDKTemplate +{ + Windows::Networking::NetworkOperators::NetworkOperatorTetheringManager TryGetCurrentNetworkOperatorTetheringManager(); + hstring GetFriendlyName(Windows::Networking::NetworkOperators::TetheringWiFiBand value); + bool IsBandSupported(Windows::Networking::NetworkOperators::NetworkOperatorTetheringAccessPointConfiguration const& configuration, Windows::Networking::NetworkOperators::TetheringWiFiBand band); +} diff --git a/Samples/MobileHotspot/cppwinrt/Scenario1_ConfigureMobileHotspot.cpp b/Samples/MobileHotspot/cppwinrt/Scenario1_ConfigureMobileHotspot.cpp new file mode 100644 index 0000000000..4efedc6b1c --- /dev/null +++ b/Samples/MobileHotspot/cppwinrt/Scenario1_ConfigureMobileHotspot.cpp @@ -0,0 +1,103 @@ +//********************************************************* +// +// 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 "Scenario1_ConfigureMobileHotspot.h" +#include "Scenario1_ConfigureMobileHotspot.g.cpp" + +using namespace winrt; +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::Networking::Connectivity; +using namespace Windows::Networking::NetworkOperators; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; +using namespace Windows::UI::Xaml::Navigation; + +namespace winrt::SDKTemplate::implementation +{ + Scenario1_ConfigureMobileHotspot::Scenario1_ConfigureMobileHotspot() + { + InitializeComponent(); + } + + void Scenario1_ConfigureMobileHotspot::OnNavigatedTo(NavigationEventArgs const&) + { + m_tetheringManager = SDKTemplate::TryGetCurrentNetworkOperatorTetheringManager(); + + if (m_tetheringManager) + { + InitializeTetheringControls(); + HotspotPanel().Visibility(Visibility::Visible); + } + } + + void Scenario1_ConfigureMobileHotspot::AddComboBoxItem(ComboBox const& comboBox, hstring name, IInspectable const& tag, bool selected) + { + ComboBoxItem item; + item.Tag(tag); + item.Content(box_value(name)); + comboBox.Items().Append(item); + if (selected) + { + comboBox.SelectedItem(item); + } + } + + void Scenario1_ConfigureMobileHotspot::InitializeTetheringControls() + { + NetworkOperatorTetheringAccessPointConfiguration configuration = m_tetheringManager.GetCurrentAccessPointConfiguration(); + + SsidTextBox().Text(configuration.Ssid()); + PassphraseTextBox().Text(configuration.Passphrase()); + + // Fill the Band combo box with supported bands, and select the current one. + TetheringWiFiBand currentBand = configuration.Band(); + BandComboBox().Items().Clear(); + for (TetheringWiFiBand band = TetheringWiFiBand::Auto; band <= TetheringWiFiBand::FiveGigahertz; band = (TetheringWiFiBand)((int)band + 1)) + { + if (SDKTemplate::IsBandSupported(configuration, band)) + { + AddComboBoxItem(BandComboBox(), band, band == currentBand); + } + } + if (BandComboBox().SelectedIndex() == -1) + { + AddComboBoxItem(BandComboBox(), currentBand, true); + } + } + + fire_and_forget Scenario1_ConfigureMobileHotspot::ApplyChanges_Click(IInspectable const&, RoutedEventArgs const& e) + { + if (m_applying) + { + co_return; + + } + m_applying = true; + + NetworkOperatorTetheringAccessPointConfiguration configuration = m_tetheringManager.GetCurrentAccessPointConfiguration(); + + configuration.Ssid(SsidTextBox().Text()); + configuration.Passphrase(PassphraseTextBox().Text()); + configuration.Band(BandComboBox().SelectedItem().as().Tag().as()); + + co_await m_tetheringManager.ConfigureAccessPointAsync(configuration); + m_applying = false; + + InitializeTetheringControls(); + } + + void Scenario1_ConfigureMobileHotspot::DiscardChanges_Click(IInspectable const&, RoutedEventArgs const&) + { + InitializeTetheringControls(); + } +} diff --git a/Samples/MobileHotspot/cppwinrt/Scenario1_ConfigureMobileHotspot.h b/Samples/MobileHotspot/cppwinrt/Scenario1_ConfigureMobileHotspot.h new file mode 100644 index 0000000000..907daac267 --- /dev/null +++ b/Samples/MobileHotspot/cppwinrt/Scenario1_ConfigureMobileHotspot.h @@ -0,0 +1,47 @@ +//********************************************************* +// +// 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 "Scenario1_ConfigureMobileHotspot.g.h" + +namespace winrt::SDKTemplate::implementation +{ + struct Scenario1_ConfigureMobileHotspot : Scenario1_ConfigureMobileHotspotT + { + Scenario1_ConfigureMobileHotspot(); + + void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs const&); + fire_and_forget ApplyChanges_Click(Windows::Foundation::IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&); + void DiscardChanges_Click(Windows::Foundation::IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&); + + private: + Windows::Networking::NetworkOperators::NetworkOperatorTetheringManager m_tetheringManager{ nullptr }; + bool m_applying{ false }; + + void InitializeTetheringControls(); + + void AddComboBoxItem(Windows::UI::Xaml::Controls::ComboBox const& comboBox, hstring name, Windows::Foundation::IInspectable const& tag, bool selected); + + template + void AddComboBoxItem(Windows::UI::Xaml::Controls::ComboBox const& comboBox, T value, bool selected) + { + AddComboBoxItem(comboBox, SDKTemplate::GetFriendlyName(value), box_value(value), selected); + } + }; +} + +namespace winrt::SDKTemplate::factory_implementation +{ + struct Scenario1_ConfigureMobileHotspot : Scenario1_ConfigureMobileHotspotT + { + }; +} diff --git a/Samples/MobileHotspot/cppwinrt/Scenario2_ToggleMobileHotspot.cpp b/Samples/MobileHotspot/cppwinrt/Scenario2_ToggleMobileHotspot.cpp new file mode 100644 index 0000000000..98be8fdb83 --- /dev/null +++ b/Samples/MobileHotspot/cppwinrt/Scenario2_ToggleMobileHotspot.cpp @@ -0,0 +1,110 @@ +//********************************************************* +// +// 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 "Scenario2_ToggleMobileHotspot.h" +#include "Scenario2_ToggleMobileHotspot.g.cpp" +#include "MainPage.h" + +using namespace winrt; +using namespace Windows::ApplicationModel::Background; +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::Networking::Connectivity; +using namespace Windows::Networking::NetworkOperators; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Navigation; + +namespace winrt::SDKTemplate::implementation +{ + Scenario2_ToggleMobileHotspot::Scenario2_ToggleMobileHotspot() + { + InitializeComponent(); + } + + void Scenario2_ToggleMobileHotspot::OnNavigatedTo(NavigationEventArgs const&) + { + MainPage::Current().NotifyUser(L"", NotifyType::StatusMessage); + + m_tetheringManager = SDKTemplate::TryGetCurrentNetworkOperatorTetheringManager(); + + if (m_tetheringManager) + { + NetworkOperatorTetheringAccessPointConfiguration configuration = m_tetheringManager.GetCurrentAccessPointConfiguration(); + SsidRun().Text(configuration.Ssid()); + PasswordRun().Text(configuration.Passphrase()); + BandRun().Text(SDKTemplate::GetFriendlyName(configuration.Band())); + + m_toggling = true; + bool isTethered = m_tetheringManager.TetheringOperationalState() == TetheringOperationalState::On; + MobileHotspotToggle().IsOn(isTethered); + m_toggling = false; + + HotspotPanel().Visibility(Visibility::Visible); + } + } + + fire_and_forget Scenario2_ToggleMobileHotspot::MobileHotspotToggle_Toggled(Windows::Foundation::IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&) + { + if (m_toggling) + { + co_return; + } + MainPage::Current().NotifyUser(L"", NotifyType::StatusMessage); + + bool isTethered = m_tetheringManager.TetheringOperationalState() == TetheringOperationalState::On; + if (MobileHotspotToggle().IsOn() == isTethered) + { + // Already in desired state. + co_return; + } + + // Switch to opposite state. + m_toggling = true; + + NetworkOperatorTetheringOperationResult result{ nullptr }; + if (isTethered) + { + result = co_await m_tetheringManager.StopTetheringAsync(); + } + else + { + result = co_await m_tetheringManager.StartTetheringAsync(); + } + + if (result.Status() == TetheringOperationStatus::Success) + { + // Change to new state. + MobileHotspotToggle().IsOn(!isTethered); + } + else + { + // Return to old state. + MobileHotspotToggle().IsOn(isTethered); + + switch (result.Status()) + { + case TetheringOperationStatus::WiFiDeviceOff: + MainPage::Current().NotifyUser( + L"Wi-Fi adapter is either turned off or missing.", + NotifyType::ErrorMessage); + break; + + default: + MainPage::Current().NotifyUser( + L"Unhandled result while toggling Mobile Hotspot: " + to_hstring(static_cast(result.Status())), // TODO: stringify + NotifyType::ErrorMessage); + break; + } + } + m_toggling = false; + } +} diff --git a/Samples/MobileHotspot/cppwinrt/Scenario2_ToggleMobileHotspot.h b/Samples/MobileHotspot/cppwinrt/Scenario2_ToggleMobileHotspot.h new file mode 100644 index 0000000000..3083372969 --- /dev/null +++ b/Samples/MobileHotspot/cppwinrt/Scenario2_ToggleMobileHotspot.h @@ -0,0 +1,36 @@ +//********************************************************* +// +// 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 "Scenario2_ToggleMobileHotspot.g.h" + +namespace winrt::SDKTemplate::implementation +{ + struct Scenario2_ToggleMobileHotspot : Scenario2_ToggleMobileHotspotT + { + Scenario2_ToggleMobileHotspot(); + + void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs const&); + fire_and_forget MobileHotspotToggle_Toggled(Windows::Foundation::IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&); + + private: + Windows::Networking::NetworkOperators::NetworkOperatorTetheringManager m_tetheringManager{ nullptr }; + bool m_toggling{ false }; + }; +} + +namespace winrt::SDKTemplate::factory_implementation +{ + struct Scenario2_ToggleMobileHotspot : Scenario2_ToggleMobileHotspotT + { + }; +} diff --git a/Samples/MobileHotspot/cppwinrt/packages.config b/Samples/MobileHotspot/cppwinrt/packages.config new file mode 100644 index 0000000000..48319b8c95 --- /dev/null +++ b/Samples/MobileHotspot/cppwinrt/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Samples/MobileHotspot/cppwinrt/pch.cpp b/Samples/MobileHotspot/cppwinrt/pch.cpp new file mode 100644 index 0000000000..01484ff5aa --- /dev/null +++ b/Samples/MobileHotspot/cppwinrt/pch.cpp @@ -0,0 +1,6 @@ +// +// pch.cpp +// Include the standard header and generate the precompiled header. +// + +#include "pch.h" diff --git a/Samples/MobileHotspot/cppwinrt/pch.h b/Samples/MobileHotspot/cppwinrt/pch.h new file mode 100644 index 0000000000..3ce39843f0 --- /dev/null +++ b/Samples/MobileHotspot/cppwinrt/pch.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include "winrt/Windows.Foundation.h" +#include "winrt/Windows.Foundation.Collections.h" +#include "winrt/Windows.ApplicationModel.Activation.h" +//#include "winrt/Windows.ApplicationModel.Background.h" +#include "winrt/Windows.Networking.Connectivity.h" +#include "winrt/Windows.Networking.NetworkOperators.h" +//#include "winrt/Windows.Security.Credentials.h" +//#include "winrt/Windows.Storage.h" +#include "winrt/Windows.System.h" +#include "winrt/Windows.UI.Core.h" +#include "winrt/Windows.UI.Xaml.h" +#include "winrt/Windows.UI.Xaml.Automation.Peers.h" +#include "winrt/Windows.UI.Xaml.Controls.h" +#include "winrt/Windows.UI.Xaml.Controls.Primitives.h" +#include "winrt/Windows.UI.Xaml.Documents.h" +//#include "winrt/Windows.UI.Xaml.Interop.h" +#include "winrt/Windows.UI.Xaml.Markup.h" +#include "winrt/Windows.UI.Xaml.Media.h" +#include "winrt/Windows.UI.Xaml.Navigation.h" +#include "SampleConfiguration.h" diff --git a/Samples/MobileHotspot/cs/MobileHotspot.csproj b/Samples/MobileHotspot/cs/MobileHotspot.csproj new file mode 100644 index 0000000000..d3913caa7e --- /dev/null +++ b/Samples/MobileHotspot/cs/MobileHotspot.csproj @@ -0,0 +1,183 @@ + + + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), LICENSE))\SharedContent + + + Debug + x86 + {3801D410-A89D-5519-882E-E97B830400DC} + AppContainerExe + Properties + MobileHotspot + MobileHotspot + en-US + UAP + 10.0.22621.0 + $(TargetPlatformVersion) + 14 + true + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UAP + ;2008 + full + ARM + false + prompt + true + + + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UAP + true + ;2008 + pdbonly + ARM + false + prompt + true + true + + + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UAP + ;2008 + full + x64 + false + prompt + true + + + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UAP + true + ;2008 + pdbonly + x64 + false + prompt + true + true + + + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UAP + ;2008 + full + x86 + false + prompt + true + + + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UAP + true + ;2008 + pdbonly + x86 + false + prompt + true + true + + + + App.xaml.cs + App.xaml + + + MainPage.xaml.cs + MainPage.xaml + + + Properties\AssemblyInfo.cs + + + + + + + + Designer + + + + + App.xaml + MSBuild:Compile + Designer + + + MainPage.xaml + MSBuild:Compile + Designer + + + Scenario1_ConfigureMobileHotspot.xaml + MSBuild:Compile + Designer + + + Scenario2_ToggleMobileHotspot.xaml + MSBuild:Compile + Designer + + + Styles\Styles.xaml + MSBuild:Compile + Designer + + + + + Properties\Default.rd.xml + + + Assets\microsoft-sdk.png + + + Assets\smallTile-sdk.png + + + Assets\splash-sdk.png + + + Assets\squareTile-sdk.png + + + Assets\storeLogo-sdk.png + + + Assets\tile-sdk.png + + + Assets\windows-sdk.png + + + + + 6.2.14 + + + + 14.0 + + + + \ No newline at end of file diff --git a/Samples/MobileHotspot/cs/MobileHotspot.sln b/Samples/MobileHotspot/cs/MobileHotspot.sln new file mode 100644 index 0000000000..ffef1c5f64 --- /dev/null +++ b/Samples/MobileHotspot/cs/MobileHotspot.sln @@ -0,0 +1,59 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileHotspot", "MobileHotspot.csproj", "{3801D410-A89D-5519-882E-E97B830400DC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3801D410-A89D-5519-882E-E97B830400DC}.Debug|Any CPU.ActiveCfg = Debug|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Debug|Any CPU.Build.0 = Debug|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Debug|Any CPU.Deploy.0 = Debug|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Debug|ARM.ActiveCfg = Debug|ARM + {3801D410-A89D-5519-882E-E97B830400DC}.Debug|ARM.Build.0 = Debug|ARM + {3801D410-A89D-5519-882E-E97B830400DC}.Debug|ARM.Deploy.0 = Debug|ARM + {3801D410-A89D-5519-882E-E97B830400DC}.Debug|ARM64.ActiveCfg = Debug|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Debug|ARM64.Build.0 = Debug|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Debug|ARM64.Deploy.0 = Debug|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Debug|x64.ActiveCfg = Debug|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Debug|x64.Build.0 = Debug|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Debug|x64.Deploy.0 = Debug|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Debug|x86.ActiveCfg = Debug|x86 + {3801D410-A89D-5519-882E-E97B830400DC}.Debug|x86.Build.0 = Debug|x86 + {3801D410-A89D-5519-882E-E97B830400DC}.Debug|x86.Deploy.0 = Debug|x86 + {3801D410-A89D-5519-882E-E97B830400DC}.Release|Any CPU.ActiveCfg = Release|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Release|Any CPU.Build.0 = Release|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Release|Any CPU.Deploy.0 = Release|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Release|ARM.ActiveCfg = Release|ARM + {3801D410-A89D-5519-882E-E97B830400DC}.Release|ARM.Build.0 = Release|ARM + {3801D410-A89D-5519-882E-E97B830400DC}.Release|ARM.Deploy.0 = Release|ARM + {3801D410-A89D-5519-882E-E97B830400DC}.Release|ARM64.ActiveCfg = Release|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Release|ARM64.Build.0 = Release|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Release|ARM64.Deploy.0 = Release|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Release|x64.ActiveCfg = Release|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Release|x64.Build.0 = Release|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Release|x64.Deploy.0 = Release|x64 + {3801D410-A89D-5519-882E-E97B830400DC}.Release|x86.ActiveCfg = Release|x86 + {3801D410-A89D-5519-882E-E97B830400DC}.Release|x86.Build.0 = Release|x86 + {3801D410-A89D-5519-882E-E97B830400DC}.Release|x86.Deploy.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {18BEEAB9-98B7-4D3C-8E8C-69BA6C727693} + EndGlobalSection +EndGlobal diff --git a/Samples/MobileHotspot/cs/Package.appxmanifest b/Samples/MobileHotspot/cs/Package.appxmanifest new file mode 100644 index 0000000000..b6962c136d --- /dev/null +++ b/Samples/MobileHotspot/cs/Package.appxmanifest @@ -0,0 +1,46 @@ + + + + + + + MobileHotspot C# Sample + Microsoft Corporation + Assets\storelogo-sdk.png + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/MobileHotspot/cs/SampleConfiguration.cs b/Samples/MobileHotspot/cs/SampleConfiguration.cs new file mode 100644 index 0000000000..3c4874c93b --- /dev/null +++ b/Samples/MobileHotspot/cs/SampleConfiguration.cs @@ -0,0 +1,150 @@ +//********************************************************* +// +// 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. +// +//********************************************************* + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Windows.Networking.Connectivity; +using Windows.Networking.NetworkOperators; +using Windows.UI.Xaml.Controls; + +namespace SDKTemplate +{ + public partial class MainPage : Page + { + public const string FEATURE_NAME = "MobileHotspot C# Sample"; + + List scenarios = new List + { + new Scenario() { Title="Configure Mobile Hotspot", ClassType=typeof(Scenario1_ConfigureMobileHotspot)}, + new Scenario() { Title="Toggle Mobile Hotspot", ClassType=typeof(Scenario2_ToggleMobileHotspot)}, + }; + } + + public class Scenario + { + public string Title { get; set; } + public Type ClassType { get; set; } + } + + public static class Helpers + { + public static NetworkOperatorTetheringManager TryGetCurrentNetworkOperatorTetheringManager() + { + // Get the connection profile associated with the internet connection currently used by the local machine. + ConnectionProfile currentConnectionProfile = NetworkInformation.GetInternetConnectionProfile(); + if (currentConnectionProfile == null) + { + MainPage.Current.NotifyUser("System is not connected to the Internet.", NotifyType.ErrorMessage); + return null; + } + + TetheringCapability tetheringCapability = + NetworkOperatorTetheringManager.GetTetheringCapabilityFromConnectionProfile(currentConnectionProfile); + + if (tetheringCapability != TetheringCapability.Enabled) + { + string message; + switch (tetheringCapability) + { + case TetheringCapability.DisabledByGroupPolicy: + message = "Tethering is disabled due to group policy."; + break; + case TetheringCapability.DisabledByHardwareLimitation: + message = "Tethering is not available due to hardware limitations."; + break; + case TetheringCapability.DisabledByOperator: + message = "Tethering operations are disabled for this account by the network operator."; + break; + case TetheringCapability.DisabledByRequiredAppNotInstalled: + message = "An application required for tethering operations is not available."; + break; + case TetheringCapability.DisabledBySku: + message = "Tethering is not supported by the current account services."; + break; + case TetheringCapability.DisabledBySystemCapability: + // This will occur if the "wiFiControl" capability is missing from the App. + message = "This app is not configured to access Wi-Fi devices on this machine."; + break; + default: + message = $"Tethering is disabled on this machine. (Code {(int)tetheringCapability})."; + break; + } + MainPage.Current.NotifyUser(message, NotifyType.ErrorMessage); + return null; + } + + const int E_NOT_FOUND = unchecked((int)0x80070490); // HRESULT_FROM_WIN32(ERROR_NOT_FOUND) + + try + { + return NetworkOperatorTetheringManager.CreateFromConnectionProfile(currentConnectionProfile); + } + catch (Exception ex) when (ex.HResult == E_NOT_FOUND) + { + MainPage.Current.NotifyUser("System has no Wi-Fi adapters.", NotifyType.ErrorMessage); + return null; + } + } + + public static string GetFriendlyName(TetheringWiFiBand value) + { + switch (value) + { + case TetheringWiFiBand.Auto: return "Any available"; + case TetheringWiFiBand.TwoPointFourGigahertz: return "2.4 GHz"; + case TetheringWiFiBand.FiveGigahertz: return "5 Ghz"; + case TetheringWiFiBand.SixGigahertz: return "6 GHz"; + default: return $"Unknown ({(uint)value})"; + } + } + + public static string GetFriendlyName(TetheringWiFiAuthenticationKind value) + { + switch (value) + { + case TetheringWiFiAuthenticationKind.Wpa2: + return "WPA2"; + case TetheringWiFiAuthenticationKind.Wpa3TransitionMode: + return "WPA2/WPA3"; + case TetheringWiFiAuthenticationKind.Wpa3: + return "WPA3"; + default: + return $"Unknown ({(uint)value})"; + } + } + + const int E_INVALID_STATE = unchecked((int)0x8007139f); // HRESULT_FROM_WIN32(ERROR_INVALID_STATE) + + // Calls NetworkOperatorTetheringAccessPointConfiguration.IsBandSupported but handles + // certain boundary cases. + + public static bool IsBandSupported(NetworkOperatorTetheringAccessPointConfiguration configuration, TetheringWiFiBand band) + { + // "Auto" mode is always supported even though it is technically not a frequency band. + if (band == TetheringWiFiBand.Auto) + { + return true; + } + + try + { + return configuration.IsBandSupported(band); + } + catch (Exception ex) when (ex.HResult == E_INVALID_STATE) + { + // The WiFi device has been disconnected. Report that we support nothing. + return false; + } + } + } +} diff --git a/Samples/MobileHotspot/cs/Scenario1_ConfigureMobileHotspot.xaml.cs b/Samples/MobileHotspot/cs/Scenario1_ConfigureMobileHotspot.xaml.cs new file mode 100644 index 0000000000..99ce86edf7 --- /dev/null +++ b/Samples/MobileHotspot/cs/Scenario1_ConfigureMobileHotspot.xaml.cs @@ -0,0 +1,100 @@ +//********************************************************* +// +// 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. +// +//********************************************************* + +using System; +using Windows.Networking.NetworkOperators; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; + +namespace SDKTemplate +{ + public sealed partial class Scenario1_ConfigureMobileHotspot : Page + { + NetworkOperatorTetheringManager m_tetheringManager = null; + bool m_applying = false; + + public Scenario1_ConfigureMobileHotspot() + { + this.InitializeComponent(); + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + m_tetheringManager = Helpers.TryGetCurrentNetworkOperatorTetheringManager(); + + if (m_tetheringManager != null) + { + InitializeTetheringControls(); + HotspotPanel.Visibility = Visibility.Visible; + } + } + + private void AddComboBoxItem(ComboBox comboBox, string name, object value, bool selected) + { + var item = new ComboBoxItem { Tag = value, Content = name }; + BandComboBox.Items.Add(item); + if (selected) + { + BandComboBox.SelectedItem = item; + } + } + + private void InitializeTetheringControls() + { + NetworkOperatorTetheringAccessPointConfiguration configuration = m_tetheringManager.GetCurrentAccessPointConfiguration(); + + SsidTextBox.Text = configuration.Ssid; + PassphraseTextBox.Text = configuration.Passphrase; + + // Fill the Band combo box with supported bands, and select the current one. + TetheringWiFiBand currentBand = configuration.Band; + BandComboBox.Items.Clear(); + for (TetheringWiFiBand band = TetheringWiFiBand.Auto; band <= TetheringWiFiBand.FiveGigahertz; band++) + { + if (Helpers.IsBandSupported(configuration, band)) + { + AddComboBoxItem(BandComboBox, Helpers.GetFriendlyName(band), band, band == currentBand); + } + } + if (BandComboBox.SelectedIndex == -1) + { + AddComboBoxItem(BandComboBox, Helpers.GetFriendlyName(currentBand), currentBand, true); + } + } + + private async void ApplyChanges_Click(object sender, RoutedEventArgs e) + { + if (m_applying) + { + return; + + } + m_applying = true; + + NetworkOperatorTetheringAccessPointConfiguration configuration = m_tetheringManager.GetCurrentAccessPointConfiguration(); + + configuration.Ssid = SsidTextBox.Text; + configuration.Passphrase = PassphraseTextBox.Text; + configuration.Band = (TetheringWiFiBand)((ComboBoxItem)BandComboBox.SelectedItem).Tag; + + await m_tetheringManager.ConfigureAccessPointAsync(configuration); + m_applying = false; + + InitializeTetheringControls(); + } + + private void DiscardChanges_Click(object sender, RoutedEventArgs e) + { + InitializeTetheringControls(); + } + } +} diff --git a/Samples/MobileHotspot/cs/Scenario2_ToggleMobileHotspot.xaml.cs b/Samples/MobileHotspot/cs/Scenario2_ToggleMobileHotspot.xaml.cs new file mode 100644 index 0000000000..0793e6929c --- /dev/null +++ b/Samples/MobileHotspot/cs/Scenario2_ToggleMobileHotspot.xaml.cs @@ -0,0 +1,108 @@ +//********************************************************* +// +// 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. +// +//********************************************************* + +using System; +using Windows.Networking.NetworkOperators; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; + +namespace SDKTemplate +{ + public sealed partial class Scenario2_ToggleMobileHotspot : Page + { + NetworkOperatorTetheringManager m_tetheringManager = null; + bool m_toggling = false; + + public Scenario2_ToggleMobileHotspot() + { + this.InitializeComponent(); + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + MainPage.Current.NotifyUser("", NotifyType.StatusMessage); + + m_tetheringManager = Helpers.TryGetCurrentNetworkOperatorTetheringManager(); + + if (m_tetheringManager != null) + { + NetworkOperatorTetheringAccessPointConfiguration configuration = m_tetheringManager.GetCurrentAccessPointConfiguration(); + SsidRun.Text = configuration.Ssid; + PasswordRun.Text = configuration.Passphrase; + BandRun.Text = Helpers.GetFriendlyName(configuration.Band); + + m_toggling = true; + bool isTethered = m_tetheringManager.TetheringOperationalState == TetheringOperationalState.On; + MobileHotspotToggle.IsOn = isTethered; + m_toggling = false; + + HotspotPanel.Visibility = Visibility.Visible; + } + } + + private async void MobileHotspotToggle_Toggled(object sender, RoutedEventArgs e) + { + if (m_toggling) + { + return; + } + MainPage.Current.NotifyUser("", NotifyType.StatusMessage); + + bool isTethered = m_tetheringManager.TetheringOperationalState == TetheringOperationalState.On; + if (MobileHotspotToggle.IsOn == isTethered) + { + // Already in desired state. + return; + } + + // Switch to opposite state. + m_toggling = true; + + NetworkOperatorTetheringOperationResult result; + if (isTethered) + { + result = await m_tetheringManager.StopTetheringAsync(); + } + else + { + result = await m_tetheringManager.StartTetheringAsync(); + } + + if (result.Status == TetheringOperationStatus.Success) + { + // Change to new state. + MobileHotspotToggle.IsOn = !isTethered; + } + else + { + // Return to old state. + MobileHotspotToggle.IsOn = isTethered; + + switch (result.Status) + { + case TetheringOperationStatus.WiFiDeviceOff: + MainPage.Current.NotifyUser( + "Wi-Fi adapter is either turned off or missing.", + NotifyType.ErrorMessage); + break; + + default: + MainPage.Current.NotifyUser( + $"Unhandled result while toggling Mobile Hotspot: {result.Status}", + NotifyType.ErrorMessage); + break; + } + } + m_toggling = false; + } + } +} diff --git a/Samples/MobileHotspot/shared/Scenario1_ConfigureMobileHotspot.xaml b/Samples/MobileHotspot/shared/Scenario1_ConfigureMobileHotspot.xaml new file mode 100644 index 0000000000..383ee11f14 --- /dev/null +++ b/Samples/MobileHotspot/shared/Scenario1_ConfigureMobileHotspot.xaml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +