From e9247157dffb68d93943c1c71a371b57a4814f88 Mon Sep 17 00:00:00 2001 From: Raymond Chen Date: Wed, 3 Jan 2018 17:00:04 -0800 Subject: [PATCH] Windows 10 Version 1709 - January 2018 Update --- README.md | 4 +- Samples/BackgroundTransfer/README.md | 1 + .../Server/website/recoverableErrors.aspx | 141 +++++++ .../BackgroundTransfer.vcxproj | 7 + .../BackgroundTransfer.vcxproj.filters | 3 + .../SampleConfiguration.cpp | 2 +- .../Scenario5_RandomAccess.xaml.cpp | 10 +- .../Scenario6_RecoverableErrors.xaml.cpp | 276 +++++++++++++ .../Scenario6_RecoverableErrors.xaml.h | 49 +++ .../BackgroundTransfer.csproj | 8 + .../BackgroundTransfer/SampleConfiguration.cs | 3 +- .../Scenario5_RandomAccess.xaml.cs | 10 +- .../Scenario6_RecoverableErrors.xaml.cs | 256 ++++++++++++ .../js/BackgroundTransfer.jsproj | 2 + .../js/html/scenario6_RecoverableErrors.html | 53 +++ .../js/js/sample-configuration.js | 3 +- .../js/js/scenario5_RandomAccess.js | 6 +- .../js/js/scenario6_RecoverableErrors.js | 214 ++++++++++ .../shared/Scenario6_RecoverableErrors.xaml | 55 +++ Samples/BasicHologram/cppwinrt/AppView.cpp | 2 +- .../cppwinrt/BasicHologram.vcxproj | 13 +- .../cppwinrt/BasicHologramMain.cpp | 7 +- .../BasicHologram/cppwinrt/Common/StepTimer.h | 58 +-- .../cppwinrt/Content/SpinningCubeRenderer.h | 2 +- Samples/BasicHologram/cppwinrt/pch.h | 7 +- Samples/ContactPanel/README.md | 64 +++ Samples/ContactPanel/cs/AppContactPanel.xaml | 45 +++ .../ContactPanel/cs/AppContactPanel.xaml.cs | 140 +++++++ Samples/ContactPanel/cs/ContactPanel.csproj | 180 +++++++++ Samples/ContactPanel/cs/ContactPanel.sln | 43 ++ Samples/ContactPanel/cs/Package.appxmanifest | 63 +++ .../ContactPanel/cs/SampleConfiguration.cs | 110 ++++++ .../cs/Scenario1_CreateContact.xaml | 34 ++ .../cs/Scenario1_CreateContact.xaml.cs | 162 ++++++++ Samples/ContactPanel/cs/project.json | 16 + .../{cpp => Service/Client}/RpcClient.cpp | 23 +- .../Service/Client/RpcClient.h | 34 ++ .../Service/Client/RpcClient.vcxproj | 243 ++++++++++++ .../Service/Client/RpcClient.vcxproj.filters | 21 + .../Service/Client/RpcClientApi.cpp | 163 ++++++++ .../Service/Client/RpcClientApi.h | 48 +++ .../CustomCapability/Service/Client/pch.cpp | 11 + Samples/CustomCapability/Service/Client/pch.h | 19 + .../Service/Client/targetver.h | 8 + .../Service/Server/RpcServer.cpp | 4 + .../Service/Server/RpcServer.vcxproj | 17 +- .../CustomCapability/cpp/CustomCapability.sln | 76 ++-- .../cpp/CustomCapability.vcxproj | 97 +++-- .../cpp/CustomCapability.vcxproj.filters | 17 +- .../CustomCapability/cpp/Package.appxmanifest | 14 +- Samples/CustomCapability/cpp/RpcClient.h | 30 -- Samples/CustomCapability/cpp/RpcInterface.c | 6 - .../cpp/Scenario1_MeteringData.xaml.cpp | 2 +- .../cpp/Scenario3_DeviceIO.xaml.cpp | 9 +- .../cpp/Scenario4_DeviceEvents.xaml.cpp | 2 +- .../cpp/Scenario5_DeviceReadWrite.xaml.cpp | 5 +- .../CustomCapability/cpp/ServiceViewModel.cpp | 56 ++- .../CustomCapability/cpp/ServiceViewModel.h | 11 +- Samples/CustomCapability/cpp/pch.cpp | 7 +- .../CustomCapability/cs/CustomCapability.SCCD | 11 + .../cs/CustomCapability.csproj | 219 +++++++++++ .../CustomCapability/cs/CustomCapability.sln | 90 +++++ Samples/CustomCapability/cs/DeviceList.cs | 320 +++++++++++++++ Samples/CustomCapability/cs/Fx2Driver.cs | 83 ++++ .../CustomCapability/cs/Package.appxmanifest | 54 +++ .../cs/SampleConfiguration.cs | 37 ++ .../cs/Scenario1_MeteringData.xaml.cs | 42 ++ .../cs/Scenario2_DeviceConnect.xaml.cs | 108 +++++ .../cs/Scenario3_DeviceIO.xaml.cs | 88 +++++ .../cs/Scenario4_DeviceEvents.xaml.cs | 212 ++++++++++ .../cs/Scenario5_DeviceReadWrite.xaml.cs | 103 +++++ .../CustomCapability/cs/ServiceViewModel.cs | 368 ++++++++++++++++++ .../CustomCapability/js/CustomCapability.SCCD | 11 + .../js/CustomCapability.jsproj | 187 +++++++++ .../CustomCapability/js/CustomCapability.sln | 104 +++++ .../js/RpcClientRt/RpcClientRt.cpp | 90 +++++ .../js/RpcClientRt/RpcClientRt.h | 38 ++ .../js/RpcClientRt/RpcClientRt.vcxproj | 237 +++++++++++ .../RpcClientRt/RpcClientRt.vcxproj.filters | 17 + .../CustomCapability/js/RpcClientRt/pch.cpp | 11 + Samples/CustomCapability/js/RpcClientRt/pch.h | 14 + .../js/css/Scenario1_MeteringData.css | 13 + .../js/css/scenario2_deviceConnect.css | 5 + .../js/css/scenario3_deviceIO.css | 5 + .../js/css/scenario4_deviceEvents.css | 5 + .../js/css/scenario5_deviceReadWrite.css | 5 + .../js/html/Scenario1_MeteringData.html | 48 +++ .../js/html/scenario2_deviceConnect.html | 49 +++ .../js/html/scenario3_deviceIO.html | 48 +++ .../js/html/scenario4_deviceEvents.html | 36 ++ .../js/html/scenario5_deviceReadWrite.html | 35 ++ .../js/js/Scenario1_MeteringData.js | 160 ++++++++ Samples/CustomCapability/js/js/deviceList.js | 237 +++++++++++ Samples/CustomCapability/js/js/fx2Driver.js | 66 ++++ .../js/js/sample-configuration.js | 19 + .../js/js/scenario2_deviceConnect.js | 190 +++++++++ .../js/js/scenario3_deviceIO.js | 81 ++++ .../js/js/scenario4_deviceEvents.js | 201 ++++++++++ .../js/js/scenario5_deviceReadWrite.js | 91 +++++ .../CustomCapability/js/package.appxmanifest | 43 ++ .../Scenario1_MeteringData.xaml | 0 .../Scenario2_DeviceConnect.xaml | 0 .../{cpp => shared}/Scenario3_DeviceIO.xaml | 0 .../Scenario4_DeviceEvents.xaml | 0 .../Scenario5_DeviceReadWrite.xaml | 0 Samples/Ink/js/css/scenario1.css | 1 + Samples/Logging/README.md | 71 ++++ .../Logging/cpp/LoggingChannelScenario.cpp | 116 ++---- Samples/Logging/cs/LoggingChannelScenario.cs | 85 +--- .../Logging/js/js/loggingChannelScenario.js | 85 +--- Samples/MixedRealityModel/README.md | 69 ++++ .../cs/MixedRealityModel.csproj | 181 +++++++++ .../cs/MixedRealityModel.sln | 43 ++ .../MixedRealityModel/cs/Package.appxmanifest | 50 +++ .../cs/SampleConfiguration.cs | 33 ++ .../cs/Scenario1_CreateTile.xaml | 76 ++++ .../cs/Scenario1_CreateTile.xaml.cs | 172 ++++++++ Samples/MyPeopleNotifications/README.md | 3 + .../cpp/Scenario1_Setup.xaml.cpp | 7 +- .../cs/Scenario1_Setup.xaml.cs | 7 +- Samples/RemoteSystems/README.md | 1 + .../RemoteSystems/cs/Scenario1_Discovery.xaml | 6 +- .../cs/Scenario1_Discovery.xaml.cs | 25 ++ .../cpp/packages.config | 4 + Samples/TouchKeyboard/README.md | 21 +- .../Shared/KeyboardDisabledTextBlock.xaml | 16 - .../Shared/KeyboardEnabledTextBlock.xaml | 16 - .../Shared/Scenario1_Launch.xaml | 73 ++-- .../Shared/Scenario2_DoNotAutoInvoke.xaml | 36 +- .../Shared/Scenario2_ShowHideEvents.xaml | 43 ++ .../Shared/Scenario3_ShowHideMethods.xaml | 51 +++ .../cpp/KeyboardDisabledTextBlock.xaml.cpp | 135 ------- .../cpp/KeyboardDisabledTextBlock.xaml.h | 37 -- .../cpp/KeyboardEnabledTextBlock.xaml.cpp | 331 ---------------- .../cpp/KeyboardEnabledTextBlock.xaml.h | 90 ----- .../TouchKeyboard/cpp/SampleConfiguration.cpp | 5 +- .../TouchKeyboard/cpp/Scenario1_Launch.xaml.h | 4 +- .../cpp/Scenario2_DoNotAutoInvoke.xaml.cpp | 52 +-- .../cpp/Scenario2_DoNotAutoInvoke.xaml.h | 5 +- .../cpp/Scenario2_ShowHideEvents.xaml.cpp | 57 +++ .../cpp/Scenario2_ShowHideEvents.xaml.h | 39 ++ .../cpp/Scenario3_ShowHideMethods.xaml.cpp | 52 +++ .../cpp/Scenario3_ShowHideMethods.xaml.h | 35 ++ .../TouchKeyboard/cpp/TouchKeyboard.vcxproj | 27 +- .../cpp/TouchKeyboard.vcxproj.filters | 19 +- Samples/TouchKeyboard/cs/CustomTextBox.cs | 32 ++ .../cs/KeyboardDisabledTextBlock.xaml.cs | 134 ------- .../cs/KeyboardEnabledTextBlock.xaml.cs | 334 ---------------- .../TouchKeyboard/cs/SampleConfiguration.cs | 5 +- .../TouchKeyboard/cs/Scenario1_Launch.xaml.cs | 1 - .../cs/Scenario2_DoNotAutoInvoke.xaml.cs | 72 ---- .../cs/Scenario2_ShowHideEvents.xaml.cs | 58 +++ .../cs/Scenario3_ShowHideMethods.xaml.cs | 55 +++ Samples/TouchKeyboard/cs/TouchKeyboard.csproj | 41 +- Samples/WebSocket/README.md | 40 +- Samples/WebSocket/cpp/Package.appxmanifest | 4 +- Samples/WebSocket/cpp/SampleConfiguration.cpp | 3 +- Samples/WebSocket/cpp/Scenario1_UTF8.xaml.cpp | 1 + .../WebSocket/cpp/Scenario2_Binary.xaml.cpp | 1 + .../Scenario3_ClientAuthentication.xaml.cpp | 284 ++++++++++++++ .../cpp/Scenario3_ClientAuthentication.xaml.h | 55 +++ Samples/WebSocket/cpp/WebSocket.vcxproj | 13 + .../WebSocket/cpp/WebSocket.vcxproj.filters | 8 + Samples/WebSocket/cs/Package.appxmanifest | 6 +- Samples/WebSocket/cs/SampleConfiguration.cs | 1 + Samples/WebSocket/cs/Scenario1_UTF8.xaml.cs | 1 + Samples/WebSocket/cs/Scenario2_Binary.xaml.cs | 1 + .../cs/Scenario3_ClientAuthentication.xaml.cs | 269 +++++++++++++ Samples/WebSocket/cs/WebSocket.csproj | 12 + Samples/WebSocket/js/Package.appxmanifest | 4 +- Samples/WebSocket/js/WebSocket.jsproj | 5 + Samples/WebSocket/js/data/placeholder.txt | 0 .../js/html/scenario3-clientCertificate.html | 47 +++ .../WebSocket/js/js/sample-configuration.js | 3 +- .../js/js/scenario3-clientCertificate.js | 223 +++++++++++ Samples/WebSocket/server/RootCert.cer | Bin 0 -> 824 bytes Samples/WebSocket/server/removeserver.ps1 | 7 +- Samples/WebSocket/server/setupserver.ps1 | 20 +- .../server/website/echowebsocket.ashx | 15 +- .../Scenario3_ClientAuthentication.xaml | 67 ++++ Samples/WebSocket/shared/clientCert.pfx | Bin 0 -> 3630 bytes .../cpp/WiFiDirectServicesWrappers.cpp | 4 +- .../cs/AppUIBasics/PageHeader.xaml | 6 +- .../cs/AppUIBasics/PageHeader.xaml.cs | 5 + .../cs/AppUIBasics/SettingsPage.xaml | 5 + SharedContent/media/microsoft-logo.glb | Bin 0 -> 3980 bytes 186 files changed, 8904 insertions(+), 1870 deletions(-) create mode 100644 Samples/BackgroundTransfer/Server/website/recoverableErrors.aspx create mode 100644 Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario6_RecoverableErrors.xaml.cpp create mode 100644 Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario6_RecoverableErrors.xaml.h create mode 100644 Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario6_RecoverableErrors.xaml.cs create mode 100644 Samples/BackgroundTransfer/js/html/scenario6_RecoverableErrors.html create mode 100644 Samples/BackgroundTransfer/js/js/scenario6_RecoverableErrors.js create mode 100644 Samples/BackgroundTransfer/shared/Scenario6_RecoverableErrors.xaml create mode 100644 Samples/ContactPanel/README.md create mode 100644 Samples/ContactPanel/cs/AppContactPanel.xaml create mode 100644 Samples/ContactPanel/cs/AppContactPanel.xaml.cs create mode 100644 Samples/ContactPanel/cs/ContactPanel.csproj create mode 100644 Samples/ContactPanel/cs/ContactPanel.sln create mode 100644 Samples/ContactPanel/cs/Package.appxmanifest create mode 100644 Samples/ContactPanel/cs/SampleConfiguration.cs create mode 100644 Samples/ContactPanel/cs/Scenario1_CreateContact.xaml create mode 100644 Samples/ContactPanel/cs/Scenario1_CreateContact.xaml.cs create mode 100644 Samples/ContactPanel/cs/project.json rename Samples/CustomCapability/{cpp => Service/Client}/RpcClient.cpp (90%) create mode 100644 Samples/CustomCapability/Service/Client/RpcClient.h create mode 100644 Samples/CustomCapability/Service/Client/RpcClient.vcxproj create mode 100644 Samples/CustomCapability/Service/Client/RpcClient.vcxproj.filters create mode 100644 Samples/CustomCapability/Service/Client/RpcClientApi.cpp create mode 100644 Samples/CustomCapability/Service/Client/RpcClientApi.h create mode 100644 Samples/CustomCapability/Service/Client/pch.cpp create mode 100644 Samples/CustomCapability/Service/Client/pch.h create mode 100644 Samples/CustomCapability/Service/Client/targetver.h delete mode 100644 Samples/CustomCapability/cpp/RpcClient.h delete mode 100644 Samples/CustomCapability/cpp/RpcInterface.c create mode 100644 Samples/CustomCapability/cs/CustomCapability.SCCD create mode 100644 Samples/CustomCapability/cs/CustomCapability.csproj create mode 100644 Samples/CustomCapability/cs/CustomCapability.sln create mode 100644 Samples/CustomCapability/cs/DeviceList.cs create mode 100644 Samples/CustomCapability/cs/Fx2Driver.cs create mode 100644 Samples/CustomCapability/cs/Package.appxmanifest create mode 100644 Samples/CustomCapability/cs/SampleConfiguration.cs create mode 100644 Samples/CustomCapability/cs/Scenario1_MeteringData.xaml.cs create mode 100644 Samples/CustomCapability/cs/Scenario2_DeviceConnect.xaml.cs create mode 100644 Samples/CustomCapability/cs/Scenario3_DeviceIO.xaml.cs create mode 100644 Samples/CustomCapability/cs/Scenario4_DeviceEvents.xaml.cs create mode 100644 Samples/CustomCapability/cs/Scenario5_DeviceReadWrite.xaml.cs create mode 100644 Samples/CustomCapability/cs/ServiceViewModel.cs create mode 100644 Samples/CustomCapability/js/CustomCapability.SCCD create mode 100644 Samples/CustomCapability/js/CustomCapability.jsproj create mode 100644 Samples/CustomCapability/js/CustomCapability.sln create mode 100644 Samples/CustomCapability/js/RpcClientRt/RpcClientRt.cpp create mode 100644 Samples/CustomCapability/js/RpcClientRt/RpcClientRt.h create mode 100644 Samples/CustomCapability/js/RpcClientRt/RpcClientRt.vcxproj create mode 100644 Samples/CustomCapability/js/RpcClientRt/RpcClientRt.vcxproj.filters create mode 100644 Samples/CustomCapability/js/RpcClientRt/pch.cpp create mode 100644 Samples/CustomCapability/js/RpcClientRt/pch.h create mode 100644 Samples/CustomCapability/js/css/Scenario1_MeteringData.css create mode 100644 Samples/CustomCapability/js/css/scenario2_deviceConnect.css create mode 100644 Samples/CustomCapability/js/css/scenario3_deviceIO.css create mode 100644 Samples/CustomCapability/js/css/scenario4_deviceEvents.css create mode 100644 Samples/CustomCapability/js/css/scenario5_deviceReadWrite.css create mode 100644 Samples/CustomCapability/js/html/Scenario1_MeteringData.html create mode 100644 Samples/CustomCapability/js/html/scenario2_deviceConnect.html create mode 100644 Samples/CustomCapability/js/html/scenario3_deviceIO.html create mode 100644 Samples/CustomCapability/js/html/scenario4_deviceEvents.html create mode 100644 Samples/CustomCapability/js/html/scenario5_deviceReadWrite.html create mode 100644 Samples/CustomCapability/js/js/Scenario1_MeteringData.js create mode 100644 Samples/CustomCapability/js/js/deviceList.js create mode 100644 Samples/CustomCapability/js/js/fx2Driver.js create mode 100644 Samples/CustomCapability/js/js/sample-configuration.js create mode 100644 Samples/CustomCapability/js/js/scenario2_deviceConnect.js create mode 100644 Samples/CustomCapability/js/js/scenario3_deviceIO.js create mode 100644 Samples/CustomCapability/js/js/scenario4_deviceEvents.js create mode 100644 Samples/CustomCapability/js/js/scenario5_deviceReadWrite.js create mode 100644 Samples/CustomCapability/js/package.appxmanifest rename Samples/CustomCapability/{cpp => shared}/Scenario1_MeteringData.xaml (100%) rename Samples/CustomCapability/{cpp => shared}/Scenario2_DeviceConnect.xaml (100%) rename Samples/CustomCapability/{cpp => shared}/Scenario3_DeviceIO.xaml (100%) rename Samples/CustomCapability/{cpp => shared}/Scenario4_DeviceEvents.xaml (100%) rename Samples/CustomCapability/{cpp => shared}/Scenario5_DeviceReadWrite.xaml (100%) create mode 100644 Samples/MixedRealityModel/README.md create mode 100644 Samples/MixedRealityModel/cs/MixedRealityModel.csproj create mode 100644 Samples/MixedRealityModel/cs/MixedRealityModel.sln create mode 100644 Samples/MixedRealityModel/cs/Package.appxmanifest create mode 100644 Samples/MixedRealityModel/cs/SampleConfiguration.cs create mode 100644 Samples/MixedRealityModel/cs/Scenario1_CreateTile.xaml create mode 100644 Samples/MixedRealityModel/cs/Scenario1_CreateTile.xaml.cs create mode 100644 Samples/SpatialInteractionSource/cpp/packages.config delete mode 100644 Samples/TouchKeyboard/Shared/KeyboardDisabledTextBlock.xaml delete mode 100644 Samples/TouchKeyboard/Shared/KeyboardEnabledTextBlock.xaml create mode 100644 Samples/TouchKeyboard/Shared/Scenario2_ShowHideEvents.xaml create mode 100644 Samples/TouchKeyboard/Shared/Scenario3_ShowHideMethods.xaml delete mode 100644 Samples/TouchKeyboard/cpp/KeyboardDisabledTextBlock.xaml.cpp delete mode 100644 Samples/TouchKeyboard/cpp/KeyboardDisabledTextBlock.xaml.h delete mode 100644 Samples/TouchKeyboard/cpp/KeyboardEnabledTextBlock.xaml.cpp delete mode 100644 Samples/TouchKeyboard/cpp/KeyboardEnabledTextBlock.xaml.h create mode 100644 Samples/TouchKeyboard/cpp/Scenario2_ShowHideEvents.xaml.cpp create mode 100644 Samples/TouchKeyboard/cpp/Scenario2_ShowHideEvents.xaml.h create mode 100644 Samples/TouchKeyboard/cpp/Scenario3_ShowHideMethods.xaml.cpp create mode 100644 Samples/TouchKeyboard/cpp/Scenario3_ShowHideMethods.xaml.h create mode 100644 Samples/TouchKeyboard/cs/CustomTextBox.cs delete mode 100644 Samples/TouchKeyboard/cs/KeyboardDisabledTextBlock.xaml.cs delete mode 100644 Samples/TouchKeyboard/cs/KeyboardEnabledTextBlock.xaml.cs delete mode 100644 Samples/TouchKeyboard/cs/Scenario2_DoNotAutoInvoke.xaml.cs create mode 100644 Samples/TouchKeyboard/cs/Scenario2_ShowHideEvents.xaml.cs create mode 100644 Samples/TouchKeyboard/cs/Scenario3_ShowHideMethods.xaml.cs create mode 100644 Samples/WebSocket/cpp/Scenario3_ClientAuthentication.xaml.cpp create mode 100644 Samples/WebSocket/cpp/Scenario3_ClientAuthentication.xaml.h create mode 100644 Samples/WebSocket/cs/Scenario3_ClientAuthentication.xaml.cs create mode 100644 Samples/WebSocket/js/data/placeholder.txt create mode 100644 Samples/WebSocket/js/html/scenario3-clientCertificate.html create mode 100644 Samples/WebSocket/js/js/scenario3-clientCertificate.js create mode 100644 Samples/WebSocket/server/RootCert.cer create mode 100644 Samples/WebSocket/shared/Scenario3_ClientAuthentication.xaml create mode 100644 Samples/WebSocket/shared/clientCert.pfx create mode 100644 SharedContent/media/microsoft-logo.glb diff --git a/README.md b/README.md index 2acb38aa24..d02af618fd 100644 --- a/README.md +++ b/README.md @@ -150,9 +150,10 @@ For additional Windows samples, see [Windows on GitHub](http://microsoft.github. Appointment calendar Contact cards - Contact picker + Contact panel + Contact picker My People notifications UserDataAccountManager @@ -444,6 +445,7 @@ For additional Windows samples, see [Windows on GitHub](http://microsoft.github. Holographic voice input Spatial interaction source Tag-along hologram + Mixed Reality Model diff --git a/Samples/BackgroundTransfer/README.md b/Samples/BackgroundTransfer/README.md index 2c663596e7..3798600f1a 100644 --- a/Samples/BackgroundTransfer/README.md +++ b/Samples/BackgroundTransfer/README.md @@ -24,6 +24,7 @@ The sample also showcases several advanced usage scenarios: - Configuring toast and tile notifications to inform the user when all transfers succeed or when at least one transfer fails. - Executing a background task when a set of uploads or downloads completes. - Accessing file content and seeking within that content while a download is still ongoing, effectively altering the order in which remote file data is requested from the server. +- Recovering from a failed download without losing already-downloaded data. **Note** Background transfer is primarily designed for long-term transfer operations for resources like video, music, and large images. For short-term operations involving transfers of smaller resources (i.e. a few KB), the HTTP APIs are recommended. [HttpClient](http://msdn.microsoft.com/library/windows/apps/dn298639) is preferred and can be used in all languages supported by UWP apps. [XHR](http://msdn.microsoft.com/library/windows/apps/br229787) can be used in JavaScript. [IXHR2](http://msdn.microsoft.com/library/windows/apps/hh770550) can be used in C++. diff --git a/Samples/BackgroundTransfer/Server/website/recoverableErrors.aspx b/Samples/BackgroundTransfer/Server/website/recoverableErrors.aspx new file mode 100644 index 0000000000..cf5d8ae98c --- /dev/null +++ b/Samples/BackgroundTransfer/Server/website/recoverableErrors.aspx @@ -0,0 +1,141 @@ +<%@ Page Language="C#" AutoEventWireup="true" Debug="true" %> + + + + + + + Recoverable Errors Download + + + Hello + + diff --git a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/BackgroundTransfer.vcxproj b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/BackgroundTransfer.vcxproj index 5bd80b9a97..b9e6cb2b0f 100644 --- a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/BackgroundTransfer.vcxproj +++ b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/BackgroundTransfer.vcxproj @@ -160,6 +160,9 @@ ..\..\shared\Scenario5_RandomAccess.xaml + + ..\..\shared\Scenario6_RecoverableErrors.xaml + @@ -173,6 +176,7 @@ + Styles\Styles.xaml @@ -213,6 +217,9 @@ ..\..\shared\Scenario5_RandomAccess.xaml + + ..\..\shared\Scenario6_RecoverableErrors.xaml + diff --git a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/BackgroundTransfer.vcxproj.filters b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/BackgroundTransfer.vcxproj.filters index b5df9e49d4..7c8124bdc2 100644 --- a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/BackgroundTransfer.vcxproj.filters +++ b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/BackgroundTransfer.vcxproj.filters @@ -22,6 +22,7 @@ + @@ -33,6 +34,7 @@ + @@ -47,6 +49,7 @@ + diff --git a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/SampleConfiguration.cpp b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/SampleConfiguration.cpp index 45d6f60934..2fb2247d9f 100644 --- a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/SampleConfiguration.cpp +++ b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/SampleConfiguration.cpp @@ -22,5 +22,5 @@ Platform::Array^ MainPage::scenariosInner = ref new Platform::Array^ sender, DownloadOperation^ progress) { - Dispatcher->RunAsync(CoreDispatcherPriority::Normal, ref new DispatchedHandler([this, progress]() - { - // We capture a snapshot of DownloadOperation.Progress because - // it is is updated in real-time while the operation is ongoing. - BackgroundDownloadProgress currentProgress = progress->Progress; + // We capture a snapshot of DownloadOperation.Progress because + // it is is updated in real-time while the operation is ongoing. + BackgroundDownloadProgress currentProgress = progress->Progress; + Dispatcher->RunAsync(CoreDispatcherPriority::Normal, ref new DispatchedHandler([this, currentProgress]() + { // Prepare the progress message to display in the UI. uint64_t percent = 100; if (currentProgress.TotalBytesToReceive > 0) diff --git a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario6_RecoverableErrors.xaml.cpp b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario6_RecoverableErrors.xaml.cpp new file mode 100644 index 0000000000..94dc2b97b6 --- /dev/null +++ b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario6_RecoverableErrors.xaml.cpp @@ -0,0 +1,276 @@ +//********************************************************* +// +// 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 "Scenario6_RecoverableErrors.xaml.h" +#include + +using namespace SDKTemplate; + +using namespace Concurrency; +using namespace Platform; +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::Networking::BackgroundTransfer; +using namespace Windows::Storage; +using namespace Windows::Storage::Streams; +using namespace Windows::UI::Core; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; +using namespace Windows::UI::Xaml::Navigation; +using namespace Windows::Web; + +Scenario6_RecoverableErrors::Scenario6_RecoverableErrors() +{ + InitializeComponent(); +} + +void Scenario6_RecoverableErrors::OnNavigatedTo(NavigationEventArgs^ e) +{ + // An application must enumerate downloads when it gets started to prevent stale downloads/uploads. + // Typically this can be done in the App class by overriding OnLaunched() and checking for + // "args.Kind == ActivationKind.Launch" to detect an actual app launch. + // We do it here in the sample to keep the sample code consolidated. + // Note that for this specific scenario we are not interested in downloads from previous instances, so + // we just enumerate all downloads from previous instances and cancel them immediately. + CancelActiveDownloadsAsync(); +} + +void Scenario6_RecoverableErrors::OnNavigatedFrom(NavigationEventArgs^ e) +{ + // Cancel any active download. + cancellationTokenSource.cancel(); + +} + +task Scenario6_RecoverableErrors::CancelActiveDownloadsAsync() +{ + return create_task(BackgroundDownloader::GetCurrentDownloadsAsync()).then([this](task^> downloadTask) + { + IVectorView^ downloads; + try + { + downloads = downloadTask.get(); + } + catch (Exception^ ex) + { + if (IsWebException("Discovery error", ex)) + { + // Don't worry if the old downloads encountered web exceptions. + return task_from_result(); + } + throw; + } + + // If previous sample instances/scenarios started transfers that haven't completed yet, cancel them now + // so that we can start this scenario cleanly. + if (downloads->Size > 0) + { + cancellation_token_source canceledToken; + canceledToken.cancel(); + + std::vector> taskList; + for (DownloadOperation^ downloadOperation : downloads) + { + taskList.push_back(create_task(downloadOperation->AttachAsync(), canceledToken.get_token()).then([](task previousTask) + { + try + { + previousTask.get(); + } + catch (task_canceled&) + { + } + })); + } + + return when_all(taskList.begin(), taskList.end()).then([this, downloads]() + { + rootPage->NotifyUser("Canceled " + downloads->Size.ToString() + " downloads.", NotifyType::StatusMessage); + }); + } + return task_from_result(); + }); +} + +void Scenario6_RecoverableErrors::OnDownloadProgress(IAsyncOperationWithProgress^ sender, DownloadOperation^ progress) +{ + // We capture a snapshot of DownloadOperation.Progress because + // it is is updated in real-time while the operation is ongoing. + BackgroundDownloadProgress currentProgress = progress->Progress; + + Dispatcher->RunAsync(CoreDispatcherPriority::Normal, ref new DispatchedHandler([this, currentProgress]() + { + // Prepare the progress message to display in the UI. + uint64_t percent = 100; + if (currentProgress.TotalBytesToReceive > 0) + { + percent = currentProgress.BytesReceived * 100 / currentProgress.TotalBytesToReceive; + } + DownloadedStatusText->Text = currentProgress.Status.ToString() + " - " + percent.ToString() + "% downloaded."; + + if (currentProgress.HasRestarted) + { + rootPage->NotifyUser("Download has restarted.", NotifyType::StatusMessage); + } + + if (currentProgress.Status == BackgroundTransferStatus::PausedRecoverableWebErrorStatus) + { + // The only value we put in RecoverableWebErrorStatuses is WebErrorStatus.Forbidden, + // so that will be the only value observed here. + assert(download->CurrentWebErrorStatus->Value == WebErrorStatus::Forbidden); + + rootPage->NotifyUser("URL has expired.", NotifyType::ErrorMessage); + + // The URL expired. Ask the user for information so we can get a new URL. + create_task(ReauthorizeDialog->ShowAsync()).then([this](ContentDialogResult result) + { + if (result == ContentDialogResult::Primary) + { + // For the purpose of this sample, we simply remove "?shouldExpire=yes" from the URL + // to indicate that the sample server should treat it like a new, unexpired URL. + std::wstring originalUrlString(download->RequestedUri->AbsoluteUri->Data()); + auto truncationPoint = originalUrlString.find(L'?'); + if (truncationPoint == std::wstring::npos) + { + truncationPoint = originalUrlString.length(); + } + auto newUrlString = ref new String(originalUrlString.c_str(), truncationPoint); + + rootPage->NotifyUser("Updating URL and resuming the download.", NotifyType::StatusMessage); + download->RequestedUri = ref new Uri(newUrlString); + download->Resume(); + } + else + { + // Cancel the download. + cancellationTokenSource.cancel(); + + // Re-create the cancellation_token_source for future downloads. + cancellationTokenSource = cancellation_token_source(); + } + }); + } + })); +} + +void Scenario6_RecoverableErrors::StartDownload_Click(Object^ sender, RoutedEventArgs^ e) +{ + // Reset the output whenever a new download attempt begins. + DownloadedInfoText->Text = ""; + DownloadedStatusText->Text = ""; + rootPage->NotifyUser("", NotifyType::StatusMessage); + + Uri^ source; + if (!rootPage->TryGetUri(serverAddressField->Text, &source)) + { + return; + } + + String^ destination = StringTrimmer::Trim(fileNameField->Text); + if (destination == "") + { + rootPage->NotifyUser("A local file name is required.", NotifyType::ErrorMessage); + return; + } + + create_task(KnownFolders::GetFolderForUserAsync(nullptr /* current user */, KnownFolderId::PicturesLibrary)) + .then([this, destination](StorageFolder^ picturesLibrary) { + return picturesLibrary->CreateFileAsync(destination, CreationCollisionOption::GenerateUniqueName); + }).then([this, source](task previousTask) + { + StorageFile^ destinationFile; + try + { + destinationFile = previousTask.get(); + } + catch (Exception^ ex) + { + rootPage->NotifyUser("Error while creating file: " + ex->Message, NotifyType::ErrorMessage); + return task_from_result(); + } + + BackgroundDownloader^ downloader = ref new BackgroundDownloader(); + download = downloader->CreateDownload(source, destinationFile); + + // Opt into "random access" mode. Transfers configured this way have full support for resumable URL updates. + // If the timestamp or file size of the updated URL is different from that of the previous URL, the download + // will restart from scratch. Otherwise, the transfer will resume from the same position using the new URL. + // + // Due to OS limitations, downloads that don't opt into "random access" mode will always restart from scratch + // whenever their URL is updated. + download->IsRandomAccessRequired = true; + + if (configureRecoverableErrorsCheckBox->IsChecked->Value) + { + // Declare HTTP 403 (WebErrorStatus.Forbidden) as a recoverable error. + download->RecoverableWebErrorStatuses->Append(WebErrorStatus::Forbidden); + } + + DownloadedInfoText->Text = "Downloading to " + destinationFile->Name + ", " + download->Guid.ToString(); + + // Start the download and wait for it to complete. + return HandleDownloadAsync(); + }); +} + +task Scenario6_RecoverableErrors::HandleDownloadAsync() +{ + startDownloadButton->IsEnabled = false; + IAsyncOperationWithProgress^ downloadOperation = download->StartAsync(); + downloadOperation->Progress = ref new AsyncOperationProgressHandler(this, &Scenario6_RecoverableErrors::OnDownloadProgress); + + return create_task(downloadOperation, cancellationTokenSource.get_token()).then([this](DownloadOperation^ download) + { + rootPage->NotifyUser("Download completed successfully.", NotifyType::StatusMessage); + }).then([this](task precedingTask) + { + try + { + precedingTask.get(); + } + catch (Exception^ ex) + { + // Abandon the operation if a web exception occurs. + if (IsWebException("Execution error", ex)) + { + return; + } + throw; + } + catch (const task_canceled&) + { + // Download was canceled. + } + }).then([this]() + { + download = nullptr; + + startDownloadButton->IsEnabled = true; + }); +} + +bool Scenario6_RecoverableErrors::IsWebException(String^ title, Exception^ ex) +{ + WebErrorStatus error = BackgroundTransferError::GetStatus(ex->HResult); + bool result = (error != WebErrorStatus::Unknown); + String^ message = result ? error.ToString() : ex->Message; + if (download == nullptr) + { + rootPage->NotifyUser(title + ": " + message, NotifyType::ErrorMessage); + } + else + { + rootPage->NotifyUser(download->Guid.ToString() + " - " + title + ": " + message, NotifyType::ErrorMessage); + } + + return result; +} diff --git a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario6_RecoverableErrors.xaml.h b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario6_RecoverableErrors.xaml.h new file mode 100644 index 0000000000..eab42a888e --- /dev/null +++ b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario6_RecoverableErrors.xaml.h @@ -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. +// +//********************************************************* + +#pragma once + +#include "pch.h" +#include "Scenario6_RecoverableErrors.g.h" +#include "MainPage.xaml.h" + +namespace SDKTemplate +{ + [Windows::Foundation::Metadata::WebHostHidden] + public ref class Scenario6_RecoverableErrors sealed + { + public: + Scenario6_RecoverableErrors(); + + protected: + virtual void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override; + virtual void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override; + + void StartDownload_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + + private: + MainPage^ rootPage = MainPage::Current; + + // Scenario instance state. + Windows::Networking::BackgroundTransfer::DownloadOperation^ download; + Concurrency::cancellation_token_source cancellationTokenSource; + + // + Concurrency::task CancelActiveDownloadsAsync(); + Concurrency::task HandleDownloadAsync(); + + bool IsWebException(Platform::String^ title, Platform::Exception^ ex); + + void OnDownloadProgress( + Windows::Foundation::IAsyncOperationWithProgress^ sender, + Windows::Networking::BackgroundTransfer::DownloadOperation^ progress); + }; +} diff --git a/Samples/BackgroundTransfer/cs/BackgroundTransfer/BackgroundTransfer.csproj b/Samples/BackgroundTransfer/cs/BackgroundTransfer/BackgroundTransfer.csproj index 288201e4bd..153ae32c63 100644 --- a/Samples/BackgroundTransfer/cs/BackgroundTransfer/BackgroundTransfer.csproj +++ b/Samples/BackgroundTransfer/cs/BackgroundTransfer/BackgroundTransfer.csproj @@ -118,6 +118,9 @@ Scenario5_RandomAccess.xaml + + Scenario6_RecoverableErrors.xaml + @@ -160,6 +163,11 @@ MSBuild:Compile Designer + + Scenario6_RecoverableErrors.xaml + MSBuild:Compile + Designer + Styles\Styles.xaml MSBuild:Compile diff --git a/Samples/BackgroundTransfer/cs/BackgroundTransfer/SampleConfiguration.cs b/Samples/BackgroundTransfer/cs/BackgroundTransfer/SampleConfiguration.cs index 5397478c6d..e76d54928d 100644 --- a/Samples/BackgroundTransfer/cs/BackgroundTransfer/SampleConfiguration.cs +++ b/Samples/BackgroundTransfer/cs/BackgroundTransfer/SampleConfiguration.cs @@ -25,7 +25,8 @@ public partial class MainPage : Page new Scenario() { Title="File Upload", ClassType=typeof(Scenario2_Upload)}, new Scenario() { Title="Completion Notifications", ClassType=typeof(Scenario3_Notifications)}, new Scenario() { Title="Completion Groups", ClassType=typeof(Scenario4_CompletionGroups)}, - new Scenario() { Title="Random Access Downloads", ClassType=typeof(Scenario5_RandomAccess)} + new Scenario() { Title="Random Access Downloads", ClassType=typeof(Scenario5_RandomAccess)}, + new Scenario() { Title="Recoverable Errors", ClassType=typeof(Scenario6_RecoverableErrors)} }; } diff --git a/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario5_RandomAccess.xaml.cs b/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario5_RandomAccess.xaml.cs index af5d9d9478..9798e921bd 100644 --- a/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario5_RandomAccess.xaml.cs +++ b/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario5_RandomAccess.xaml.cs @@ -162,12 +162,12 @@ private static string FormatDownloadedRanges(IEnumerable sender, DownloadOperation progress) { + // We capture a snapshot of DownloadOperation.Progress because + // it is is updated in real-time while the operation is ongoing. + BackgroundDownloadProgress currentProgress = progress.Progress; + await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { - // We capture a snapshot of DownloadOperation.Progress because - // it is is updated in real-time while the operation is ongoing. - BackgroundDownloadProgress currentProgress = progress.Progress; - // Prepare the progress message to display in the UI. ulong percent = 100; if (currentProgress.TotalBytesToReceive > 0) @@ -233,7 +233,7 @@ private async void StartDownload_Click(object sender, RoutedEventArgs e) return; } - var picturesLibrary = await KnownFolders.GetFolderForUserAsync(null /* current user */, KnownFolderId.PicturesLibrary); + StorageFolder picturesLibrary = await KnownFolders.GetFolderForUserAsync(null /* current user */, KnownFolderId.PicturesLibrary); StorageFile destinationFile; try { diff --git a/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario6_RecoverableErrors.xaml.cs b/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario6_RecoverableErrors.xaml.cs new file mode 100644 index 0000000000..57cb1e9bc3 --- /dev/null +++ b/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario6_RecoverableErrors.xaml.cs @@ -0,0 +1,256 @@ +//********************************************************* +// +// 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.Threading; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.Networking.BackgroundTransfer; +using Windows.Storage; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; +using Windows.Web; + +namespace SDKTemplate +{ + public sealed partial class Scenario6_RecoverableErrors : Page + { + private MainPage rootPage = MainPage.Current; + + // Scenario instance state. + private DownloadOperation download = null; + private CancellationTokenSource downloadCancellationTokenSource = new CancellationTokenSource(); + + public Scenario6_RecoverableErrors() + { + InitializeComponent(); + } + + /// + /// Invoked when this page is about to be displayed in a Frame. + /// + /// Event data that describes how this page was reached. The Parameter + /// property is typically used to configure the page. + protected async override void OnNavigatedTo(NavigationEventArgs e) + { + // An application must enumerate downloads when it gets started to prevent stale downloads/uploads. + // Typically this can be done in the App class by overriding OnLaunched() and checking for + // "args.Kind == ActivationKind.Launch" to detect an actual app launch. + // We do it here in the sample to keep the sample code consolidated. + // Note that for this specific scenario we are not interested in downloads from previous instances, so + // we just enumerate all downloads from previous instances and cancel them immediately. + await CancelActiveDownloadsAsync(); + } + + protected override void OnNavigatedFrom(NavigationEventArgs e) + { + downloadCancellationTokenSource.Cancel(); + } + + private async Task CancelActiveDownloadsAsync() + { + IReadOnlyList downloads; + try + { + downloads = await BackgroundDownloader.GetCurrentDownloadsAsync(); + } + catch (Exception ex) when (IsWebException("Discovery error", ex)) + { + // Don't worry if the old downloads encountered web exceptions. + return; + } + + // If previous sample instances/scenarios started transfers that haven't completed yet, cancel them now + // so that we can start this scenario cleanly. + if (downloads.Count > 0) + { + using (CancellationTokenSource canceledToken = new CancellationTokenSource()) + { + canceledToken.Cancel(); + + Task[] tasks = new Task[downloads.Count]; + for (int i = 0; i < downloads.Count; i++) + { + tasks[i] = downloads[i].AttachAsync().AsTask(canceledToken.Token); + } + + try + { + await Task.WhenAll(tasks); + } + catch (TaskCanceledException) + { + } + } + + rootPage.NotifyUser($"Canceled {downloads.Count} downloads.", NotifyType.StatusMessage); + } + } + + private async void OnDownloadProgress(IAsyncOperationWithProgress sender, DownloadOperation progress) + { + // We capture a snapshot of DownloadOperation.Progress because + // it is is updated in real-time while the operation is ongoing. + BackgroundDownloadProgress currentProgress = progress.Progress; + + await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => + { + // Prepare the progress message to display in the UI. + ulong percent = 100; + if (currentProgress.TotalBytesToReceive > 0) + { + percent = currentProgress.BytesReceived * 100 / currentProgress.TotalBytesToReceive; + } + + DownloadedStatusText.Text = $"{currentProgress.Status} - {percent}% downloaded."; + + if (currentProgress.HasRestarted) + { + rootPage.NotifyUser("Download has restarted.", NotifyType.StatusMessage); + } + + if (currentProgress.Status == BackgroundTransferStatus.PausedRecoverableWebErrorStatus) + { + // The only value we put in RecoverableWebErrorStatuses is WebErrorStatus.Forbidden, + // so that will be the only value observed here. + System.Diagnostics.Debug.Assert(download.CurrentWebErrorStatus == WebErrorStatus.Forbidden); + + rootPage.NotifyUser("URL has expired.", NotifyType.ErrorMessage); + + // The URL expired. Ask the user for information so we can get a new URL. + ContentDialogResult result = await ReauthorizeDialog.ShowAsync(); + + if (result == ContentDialogResult.Primary) + { + // For the purpose of this sample, we simply remove "?shouldExpire=yes" from the URL + // to indicate that the sample server should treat it like a new, unexpired URL. + string originalUrlString = download.RequestedUri.OriginalString; + string newUrlString = originalUrlString.Replace("?shouldExpire=yes", ""); + + rootPage.NotifyUser("Updating URL and resuming the download.", NotifyType.StatusMessage); + download.RequestedUri = new Uri(newUrlString); + download.Resume(); + } + else + { + // Cancel the download. + downloadCancellationTokenSource.Cancel(); + downloadCancellationTokenSource.Dispose(); + + // Re-create the CancellationTokenSource for future downloads. + downloadCancellationTokenSource = new CancellationTokenSource(); + } + } + }); + } + + private async void StartDownload_Click(object sender, RoutedEventArgs e) + { + // Reset the output whenever a new download attempt begins. + DownloadedInfoText.Text = ""; + DownloadedStatusText.Text = ""; + rootPage.NotifyUser("", NotifyType.StatusMessage); + + Uri source; + if (!Uri.TryCreate(serverAddressField.Text.Trim(), UriKind.Absolute, out source)) + { + rootPage.NotifyUser("Invalid URI.", NotifyType.ErrorMessage); + return; + } + + string destination = fileNameField.Text.Trim(); + if (string.IsNullOrWhiteSpace(destination)) + { + rootPage.NotifyUser("A local file name is required.", NotifyType.ErrorMessage); + return; + } + + StorageFolder picturesLibrary = await KnownFolders.GetFolderForUserAsync(null /* current user */, KnownFolderId.PicturesLibrary); + StorageFile destinationFile; + try + { + destinationFile = await picturesLibrary.CreateFileAsync(destination, CreationCollisionOption.GenerateUniqueName); + } + catch (Exception ex) + { + rootPage.NotifyUser($"Error while creating file: {ex.Message}", NotifyType.ErrorMessage); + return; + } + + BackgroundDownloader downloader = new BackgroundDownloader(); + download = downloader.CreateDownload(source, destinationFile); + + // Opt into "random access" mode. Transfers configured this way have full support for resumable URL updates. + // If the timestamp or file size of the updated URL is different from that of the previous URL, the download + // will restart from scratch. Otherwise, the transfer will resume from the same position using the new URL. + // + // Due to OS limitations, downloads that don't opt into "random access" mode will always restart from scratch + // whenever their URL is updated. + download.IsRandomAccessRequired = true; + + if (configureRecoverableErrorsCheckBox.IsChecked.Value) + { + // Declare HTTP 403 (WebErrorStatus.Forbidden) as a recoverable error. + download.RecoverableWebErrorStatuses.Add(WebErrorStatus.Forbidden); + } + + DownloadedInfoText.Text = $"Downloading to {destinationFile.Name}, {download.Guid}"; + + // Start the download and wait for it to complete. + await HandleDownloadAsync(); + } + + private async Task HandleDownloadAsync() + { + try + { + startDownloadButton.IsEnabled = false; + IAsyncOperationWithProgress downloadOperation = download.StartAsync(); + downloadOperation.Progress += OnDownloadProgress; + await downloadOperation.AsTask(downloadCancellationTokenSource.Token); + + rootPage.NotifyUser("Download completed successfully", NotifyType.StatusMessage); + } + catch (Exception ex) when (IsWebException("Execution error", ex)) + { + } + catch (TaskCanceledException) + { + // Download was canceled. + } + finally + { + download = null; + startDownloadButton.IsEnabled = true; + } + } + + private bool IsWebException(string title, Exception ex) + { + WebErrorStatus error = BackgroundTransferError.GetStatus(ex.HResult); + bool result = (error != WebErrorStatus.Unknown); + string message = result ? error.ToString() : ex.Message; + if (download == null) + { + rootPage.NotifyUser($"{title}: {message}", NotifyType.ErrorMessage); + } + else + { + rootPage.NotifyUser($"{download.Guid} - {title}: {message}", NotifyType.ErrorMessage); + } + + return result; + } + } +} diff --git a/Samples/BackgroundTransfer/js/BackgroundTransfer.jsproj b/Samples/BackgroundTransfer/js/BackgroundTransfer.jsproj index 269d33eb1c..d7bdbcf533 100644 --- a/Samples/BackgroundTransfer/js/BackgroundTransfer.jsproj +++ b/Samples/BackgroundTransfer/js/BackgroundTransfer.jsproj @@ -86,6 +86,7 @@ + @@ -93,6 +94,7 @@ + Microsoft.WinJS.4.0\css\ui-dark.css diff --git a/Samples/BackgroundTransfer/js/html/scenario6_RecoverableErrors.html b/Samples/BackgroundTransfer/js/html/scenario6_RecoverableErrors.html new file mode 100644 index 0000000000..cec52db287 --- /dev/null +++ b/Samples/BackgroundTransfer/js/html/scenario6_RecoverableErrors.html @@ -0,0 +1,53 @@ + + + + + + + + +

Description:

+
Recoverable errors
+

+ Apps can specify a set of WebErrorStatus values that can be resolved by the app, possibly with user + intervention. Instead of failing immediately, downloads transition to pausedRecoverableWebErrorStatus + if they encounter any of the app-specified errors, at which point they can be reconfigured and retried without + the loss of already-downloaded content. +

+

+ The sample server provided with this sample provides a time-limited URL. + If you check the box below, then + when the URL expires, the app switches to different URL to continue downloading. +

+

+ + +

+

+ + +

+

+ +

+

+ +

+
+
Download status:
+ + diff --git a/Samples/BackgroundTransfer/js/js/sample-configuration.js b/Samples/BackgroundTransfer/js/js/sample-configuration.js index fce1678565..b8f7c4f50c 100644 --- a/Samples/BackgroundTransfer/js/js/sample-configuration.js +++ b/Samples/BackgroundTransfer/js/js/sample-configuration.js @@ -18,7 +18,8 @@ { url: "/html/scenario2_Upload.html", title: "File Upload" }, { url: "/html/scenario3_Notifications.html", title: "Completion Notifications" }, { url: "/html/scenario4_CompletionGroups.html", title: "Completion Groups" }, - { url: "/html/scenario5_RandomAccess.html", title: "Random Access" } + { url: "/html/scenario5_RandomAccess.html", title: "Random Access" }, + { url: "/html/scenario6_RecoverableErrors.html", title: "Recoverable Errors" } ]; // Look up the name for an enumeration member. diff --git a/Samples/BackgroundTransfer/js/js/scenario5_RandomAccess.js b/Samples/BackgroundTransfer/js/js/scenario5_RandomAccess.js index 94ec6034ef..06083c2484 100644 --- a/Samples/BackgroundTransfer/js/js/scenario5_RandomAccess.js +++ b/Samples/BackgroundTransfer/js/js/scenario5_RandomAccess.js @@ -102,7 +102,7 @@ } }, function (e) { // Don't worry if the old downloads encountered web exceptions. - return allowWebException("Discovery error"); + return allowWebException("Discovery error", e); }); } @@ -283,8 +283,8 @@ }).then(function () { WinJS.log("Download completed successfully", "sample", "status"); }, function (e) { - // Don't worry if the old downloads encountered web exceptions. - return allowWebException("Discovery error"); + // Abandon the operation if a web exception occurs. + return allowWebException("Execution error", e); }).then(function () { download = null; randomAccessStream = null; diff --git a/Samples/BackgroundTransfer/js/js/scenario6_RecoverableErrors.js b/Samples/BackgroundTransfer/js/js/scenario6_RecoverableErrors.js new file mode 100644 index 0000000000..2b9a49e790 --- /dev/null +++ b/Samples/BackgroundTransfer/js/js/scenario6_RecoverableErrors.js @@ -0,0 +1,214 @@ +//********************************************************* +// +// 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. +// +//********************************************************* + +(function () { + "use strict"; + + var BackgroundDownloader = Windows.Networking.BackgroundTransfer.BackgroundDownloader; + var BackgroundTransferStatus = Windows.Networking.BackgroundTransfer.BackgroundTransferStatus; + var BackgroundTransferError = Windows.Networking.BackgroundTransfer.BackgroundTransferError; + var WebErrorStatus = Windows.Web.WebErrorStatus; + + var startDownloadButton; + var configureRecoverableErrorsCheckBox; + var downloadedInfoText; + var downloadStatusText; + + var download; + var downloadPromise; + + var page = WinJS.UI.Pages.define("/html/scenario6_RecoverableErrors.html", { + ready: function (element, options) { + startDownloadButton = document.getElementById("startDownloadButton"); + configureRecoverableErrorsCheckBox = document.getElementById("configureRecoverableErrorsCheckBox"); + downloadedInfoText = document.getElementById("downloadedInfoText"); + downloadStatusText = document.getElementById("downloadStatusText"); + + startDownloadButton.addEventListener("click", startDownload_click); + }, + unload: function (element) { + // Cancel any outstanding download. + if (downloadPromise) { + downloadPromise.cancel(); + } + } + }); + + // Helper function to allow web exceptions but propagate all other exceptions. + function allowWebException(title, e) { + if (isWebException(title, e)) { + return; + } else { + // Propagate the exception. + return WinJS.Promise.wrapError(e); + } + } + + function cancelActiveDownloadsAsync() { + return BackgroundDownloader.getCurrentDownloadsAsync().then(function (downloads) { + // If previous sample instances/scenarios started transfers that haven't completed yet, cancel them now + // so that we can start this scenario cleanly. + if (downloads.length > 0) { + var promises = downloads.map(function (downloadOperation) { + // Cancel each download and ignore the cancellation exception. + var downloadPromise = downloadOperation.attachAsync(); + downloadPromise.cancel(); + return downloadPromise.then(null, function (e) { + if (e.name === "Canceled") { + return; + } else { + // Propagate the exception. + return WinJS.Promise.wrapError(e); + } + }); + }) + return WinJS.Promise.join(promises).then(function () { + WinJS.log(`Canceled ${downloads.length} downloads.`, "sample", "status"); + }); + } + }, function (e) { + // Don't worry if the old downloads encountered web exceptions. + return allowWebException("Discovery error", e); + }); + } + + function onDownloadProgress(progress) { + // We capture a snapshot of DownloadOperation.Progress because + // it is is updated in real-time while the operation is ongoing. + var currentProgress = progress.progress; + + // Prepare the progress message to display in the UI. + var percent = 100; + if (currentProgress.totalBytesToReceive > 0) { + percent = Math.floor(currentProgress.bytesReceived * 100 / currentProgress.totalBytesToReceive); + } + downloadStatusText.innerText = `${SdkSample.lookupEnumName(BackgroundTransferStatus, currentProgress.status)} - ${percent}% downloaded.`; + + if (currentProgress.hasRestarted) { + WinJS.log("Download has restarted.", "sample", "status"); + } + + if (currentProgress.status === BackgroundTransferStatus.pausedRecoverableWebErrorStatus) { + // The only value we put in recoverableWebErrorStatuses is WebErrorStatus.forbidden, + // so that will be the only value observed here. + console.assert(download.currentWebErrorStatus === WebErrorStatus.forbidden); + + WinJS.log("URL has expired.", "sample", "error"); + + // The URL expired. Ask the user for information so we can get a new URL. + // A real program might prompt for an authorization key. + var dialog = new Windows.UI.Popups.MessageDialog("The download URL has expired. Click OK to resume the download.", + "Reauthorize download"); + dialog.commands.push(new Windows.UI.Popups.UICommand("OK", null, true)); + dialog.commands.push(new Windows.UI.Popups.UICommand("Cancel", null, false)); + dialog.defaultCommandIndex = 0; + dialog.cancelCommandIndex = 1; + dialog.showAsync().done(function (result) { + if (result.id) { + // For the purpose of this sample, we simply remove "?shouldExpire=yes" from the URL + // to indicate that the sample server should treat it like a new, unexpired URL. + var originalUrlString = download.requestedUri.absoluteUri; + var newUrlString = originalUrlString.replace("?shouldExpire=yes", ""); + + WinJS.log("Updating URL and resuming the download.", "sample", "status"); + download.requestedUri = new Windows.Foundation.Uri(newUrlString); + download.resume(); + } else { + // Cancel the download. + downloadPromise.cancel(); + } + }); + } + } + + function startDownload_click() { + // Reset the output whenever a new download attempt begins. + downloadedInfoText.innerText = ""; + downloadStatusText.innerText = ""; + WinJS.log("", "sample", "status"); + + // The URI is validated by catching exceptions thrown by the Uri constructor. + var uri = null; + try { + uri = new Windows.Foundation.Uri(document.getElementById("serverAddressField").value); + } catch (error) { + WinJS.log("Error: Invalid URI. " + error.message, "sample", "error"); + return; + } + + var destination = document.getElementById("fileNameField").value.trim(); + if (destination == "") { + WinJS.log("A local file name is required.", "sample", "error"); + return; + } + + Windows.Storage.KnownFolders.getFolderForUserAsync(null /* current user */, Windows.Storage.KnownFolderId.picturesLibrary).then(function (picturesLibrary) { + return picturesLibrary.createFileAsync(destination, Windows.Storage.CreationCollisionOption.generateUniqueName); + }).then(function (destinationFile) { + var downloader = new BackgroundDownloader(); + download = downloader.createDownload(uri, destinationFile); + + // Opt into "random access" mode. Transfers configured this way have full support for resumable URL updates. + // If the timestamp or file size of the updated URL is different from that of the previous URL, the download + // will restart from scratch. Otherwise, the transfer will resume from the same position using the new URL. + // + // Due to OS limitations, downloads that don't opt into "random access" mode will always restart from scratch + // whenever their URL is updated. + download.isRandomAccessRequired = true; + + if (configureRecoverableErrorsCheckBox.checked) { + // Declare HTTP 403 (WebErrorStatus.Forbidden) as a recoverable error. + download.recoverableWebErrorStatuses.push(WebErrorStatus.forbidden); + } + + downloadedInfoText.innerText = `Downloading to ${destinationFile.name}, ${download.guid}`; + + // Start the download and wait for it to complete. + return handleDownloadAsync(); + }, function (e) { + WinJS.log(`Error while creating file: ${e.message}`, "sample", "error"); + }).done(); + } + + function handleDownloadAsync() { + startDownloadButton.disabled = true; + + downloadPromise = download.startAsync(); + // Wait for the download to complete. + return downloadPromise.then(function () { + WinJS.log("Download completed successfully", "sample", "status"); + }, function (e) { + if (e.name === "Canceled") { + // Download was canceled. + return; + } + // Abandon the operation if a web exception occurs. + return allowWebException("Execution error", e); + }, onDownloadProgress).then(function () { + download = null; + startDownloadButton.disabled = false; + }); + } + + function isWebException(title, ex) { + var error = BackgroundTransferError.getStatus(ex.number); + var result = (error != WebErrorStatus.unknown); + var message = isWebException ? SdkSample.lookupEnumName(WebErrorStatus, error) : e.message; + if (!download) { + WinJS.log(`${title}: ${message}`, "sample", "error"); + } else { + WinJS.log(`${download.guid} - ${title}: ${message}`, "sample", "error"); + } + + return result; + } + +})(); diff --git a/Samples/BackgroundTransfer/shared/Scenario6_RecoverableErrors.xaml b/Samples/BackgroundTransfer/shared/Scenario6_RecoverableErrors.xaml new file mode 100644 index 0000000000..1887d2bfbe --- /dev/null +++ b/Samples/BackgroundTransfer/shared/Scenario6_RecoverableErrors.xaml @@ -0,0 +1,55 @@ + + + + + + + Recoverable Errors + + + Apps can specify a set of WebErrorStatus values that can be resolved by the app, possibly with user + intervention. Instead of failing immediately, downloads transition to PausedRecoverableWebErrorStatus + if they encounter any of the app-specified errors, at which point they can be reconfigured and retried without + the loss of already-downloaded content. + + + The sample server provided with this sample provides a time-limited URL. + If you check the box below, then + when the URL expires, the app switches to different URL to continue downloading. + + + + + + + + + +
+

Step 3: Observe the expected and actual incoming sample rate

+

Expected Sample Rate:

+

Actual Sample Rate:

+
+ +
+

Samples:

+
+
+ + + \ No newline at end of file diff --git a/Samples/CustomCapability/js/html/scenario2_deviceConnect.html b/Samples/CustomCapability/js/html/scenario2_deviceConnect.html new file mode 100644 index 0000000000..b27e645e86 --- /dev/null +++ b/Samples/CustomCapability/js/html/scenario2_deviceConnect.html @@ -0,0 +1,49 @@ + + + + + + + + + + + +
+
+
+
+

This scenario shows how to use a Windows.Devices.Enumeration.DeviceWatcher to detect the arrival, and removal, of a device with a custom device interface, and how to open it using the Windows.Devices.Custom.CustomDevice class.

+

The device interface is found using the device interface GUID chosen by the custom driver. This is defined, along with all other driver-defined constants, in the fx2Driver object (fx2Driver.js).

+

Select an Fx2 Device:

+ +

+
+
+
+
+

+
+
+
+
+ + diff --git a/Samples/CustomCapability/js/html/scenario3_deviceIO.html b/Samples/CustomCapability/js/html/scenario3_deviceIO.html new file mode 100644 index 0000000000..446bfbb53a --- /dev/null +++ b/Samples/CustomCapability/js/html/scenario3_deviceIO.html @@ -0,0 +1,48 @@ + + + + + + + + + + + +
+

This scenario shows how to use I/O controls to perform simple operations with a device driver.

+

The buttons below use the SendIOControlAsync method to communicate with the device driver, and set or get the value of the seven-segment display on the device.

+

The IO control codes used in this scenario, along with all other driver-defined constants, in the fx2Driver object(fx2Driver.js).

+
+

+ + +

+
+
+
+
+
+ + diff --git a/Samples/CustomCapability/js/html/scenario4_deviceEvents.html b/Samples/CustomCapability/js/html/scenario4_deviceEvents.html new file mode 100644 index 0000000000..68647048ea --- /dev/null +++ b/Samples/CustomCapability/js/html/scenario4_deviceEvents.html @@ -0,0 +1,36 @@ + + + + + + + + + + + +
+

This scenario shows how to use SendIOControlAsync to receive asynchronous events from the device driver.

+

The Begin Receiving Switch Change Events button below sends an get interrupt message IO control to the driver. The driver completes this only when the DIP switches on the Fx2 have changed. On completion the sample code updates the UI and then issues another get interrupt IO control. The Stop Receiving Switch Change Events button cancels the pending IO control. The IO control is also canceled if the scenario is changed. If the device is unplugged or disconnected the pending IO control completes with an error, and the sample code stops retrieving events.

+

The IO control codes used in this scenario, along with all other driver-defined constants, in the fx2Driver object(fx2Driver.js)

+

+

+

+
+
+
+
+
+ + diff --git a/Samples/CustomCapability/js/html/scenario5_deviceReadWrite.html b/Samples/CustomCapability/js/html/scenario5_deviceReadWrite.html new file mode 100644 index 0000000000..c43417f857 --- /dev/null +++ b/Samples/CustomCapability/js/html/scenario5_deviceReadWrite.html @@ -0,0 +1,35 @@ + + + + + + + + + + + +
+

This scenario shows how to use the InputStream and OutputStream properties of Windows.Devices.CustomDevice to send read and write commands to the device driver.

+

The Write Block button writes a message to the Fx2 device's internal memory, and the call to WriteAsync completes once the driver has committed the data to the device. The device has four message buffers, and once they are full the next write will be blocked until a buffer is free.

+

The Read Button reads a message from the memory. The read operation will block if no messages are available, otherwise the driver will read one message and free the slot. This may allow a pending write to complete.

+

+

+
+
+

Output Log:

+
+
+ + diff --git a/Samples/CustomCapability/js/js/Scenario1_MeteringData.js b/Samples/CustomCapability/js/js/Scenario1_MeteringData.js new file mode 100644 index 0000000000..ae8ff2c58d --- /dev/null +++ b/Samples/CustomCapability/js/js/Scenario1_MeteringData.js @@ -0,0 +1,160 @@ +//// Copyright (c) Microsoft Corporation. All rights reserved + +(function () { + "use strict"; + + // Instance of WinRT component that implements + // RPC metering service client + var rpcClient = new RpcClientRt.RpcClient; + var meteringOn = false; + var meteringPageInForeground = false; + var samplePeriod = 100; + var meteringSuspended = false; + var meteringIntervalId = 0; + var meteringCallbackCount = 0; + var lastMeteringCallbackTime = 0; + + function updateStartStopButtons() { + document.getElementById("StartButton").disabled = meteringOn; + document.getElementById("StopButton").disabled = !meteringOn; + } + + var page = WinJS.UI.Pages.define("/html/Scenario1_MeteringData.html", { + ready: function (element, options) { + // Attach event handlers to html input elements + document.getElementById("StartButton").addEventListener("click", onStartButtonClick, false); + document.getElementById("StopButton").addEventListener("click", onStopButtonClick, false); + document.getElementById("SamplePeriodSlider").addEventListener("change", onSampleSliderChange, false); + + // Set up initial state + document.getElementById("SamplePeriodSlider").value = samplePeriod; + updateStartStopButtons(); + document.getElementById("SamplePeriod").innerHTML = "Sample Period (ms): " + samplePeriod; + + // Attach handlers for suspension to stop the watcher when the App is suspended. + Windows.UI.WebUI.WebUIApplication.addEventListener("suspending", onSuspending, false); + Windows.UI.WebUI.WebUIApplication.addEventListener("resuming", onResuming, false); + meteringPageInForeground = true; + }, + unload: function (element, options) { + // Remove local suspension handlers from the App since this page is no longer active. + Windows.UI.WebUI.WebUIApplication.removeEventListener("suspending", onSuspending); + Windows.UI.WebUI.WebUIApplication.removeEventListener("resuming", onResuming); + + meteringPageInForeground = false; + } + }); + + /// + /// Invoked when application execution is being suspended. Application state is saved + /// without knowing whether the application will be terminated or resumed with the contents + /// of memory still intact. + /// + /// Details about the suspend request. + function onSuspending(args) { + // Make sure to stop metering if it was running and + // set a flag that would indicate it + if (meteringOn) { + meteringSuspended = true; + onStopButtonClick(null); + } + } + + /// + /// Invoked when application execution is being resumed. + /// + /// + function onResuming(args) { + // If metering was running during suspension + // then restart it + if (meteringSuspended) { + meteringSuspended = false; + onStartButtonClick(args); + } + } + + /// + /// Returns a promise which checks for metering status + /// every 100 ms and updates the metering data in html + /// + function CheckMeteringStatusAsync() { + return new WinJS.Promise(function (c) { + lastMeteringCallbackTime = (new Date()).getTime(); + meteringIntervalId = setInterval(() => { + var ret = rpcClient.getMeteringStatus(); + if (ret) { + meteringOn = false; + + if (meteringPageInForeground) { + updateStartStopButtons(); + WinJS.log && WinJS.log("Error occured while communicating with RPC server: " + ret, "sample", "error"); + } + clearInterval(meteringIntervalId); + meteringIntervalId = 0; + } + var currentCallbackCount = rpcClient.getCallbackCount(); + if (currentCallbackCount != meteringCallbackCount) { + + var now = (new Date()).getTime(); + var diff = now - lastMeteringCallbackTime; + lastMeteringCallbackTime = now; + + if (meteringPageInForeground) { + document.getElementById("SampleMessage").innerHTML = rpcClient.getMeteringData(); + document.getElementById("ExpectedSampleRate").innerHTML = "Expected Sample Rate: " + 1000 / samplePeriod; + document.getElementById("ActualSampleRate").innerHTML = "Actual Sample Rate: " + (currentCallbackCount - meteringCallbackCount) * 1000 / diff; + } + meteringCallbackCount = currentCallbackCount; + } + }, 100); + }); + } + + // Called when the user drags the scale factor slider control. + function onSampleSliderChange(args) { + samplePeriod = Math.floor(document.getElementById("SamplePeriodSlider").value); + document.getElementById("SamplePeriod").innerHTML = "Sample Period (ms): " + samplePeriod + if (meteringOn) { + var ret = rpcClient.setSampleRate(samplePeriod); + if (ret != 0) { + WinJS.log && WinJS.log("Error occured while communicating with RPC server: " + ret, "sample", "error"); + } + else { + WinJS.log && WinJS.log("Sample period set to: " + samplePeriod, "sample", "status"); + } + } + } + + /// + /// Invoked as an event handler when the Run button is pressed. + /// + /// Event data describing the conditions that led to the event. + function onStartButtonClick(args) { + meteringOn = true; + updateStartStopButtons(); + samplePeriod = Math.floor(document.getElementById("SamplePeriodSlider").value); + rpcClient.startMeteringAsync(samplePeriod); + CheckMeteringStatusAsync(); + + WinJS.log && WinJS.log("Metering start command sent successfully", "sample", "status"); + } + + /// + /// Invoked as an event handler when the Stop button is pressed. + /// + /// Event data describing the conditions that led to the event. + function onStopButtonClick(args) { + clearInterval(meteringIntervalId); + meteringIntervalId = 0; + var ret = rpcClient.stopMeteringData(); + if (ret != 0) { + WinJS.log && WinJS.log("Error occured while communicating with RPC server: " + ret, "sample", "error"); + } + else { + WinJS.log && WinJS.log("Metering stop command sent successfully", "sample", "status"); + } + + meteringOn = false; + updateStartStopButtons(); + } +})(); diff --git a/Samples/CustomCapability/js/js/deviceList.js b/Samples/CustomCapability/js/js/deviceList.js new file mode 100644 index 0000000000..ba21a08794 --- /dev/null +++ b/Samples/CustomCapability/js/js/deviceList.js @@ -0,0 +1,237 @@ +//// Copyright (c) Microsoft Corporation. All rights reserved + +(function () { + "use strict"; + + var _fx2Watcher; + var _fx2Devices = new WinJS.Binding.List(); + + // + // Define a class to hold entries in the list of devices. + // + var DeviceListEntry = WinJS.Class.define( + function (deviceInterface) { + this.deviceInterface = deviceInterface; + this.matched = true; + }, + { + id: { get: function () { return this.deviceInterface.id; } }, + instanceId: { get: function () { return this.deviceInterface.properties["System.Devices.DeviceInstanceId"]; } }, + device: { get: function () { return this.deviceInterface; } } + }, + { + findInList: function (id) { + var match = null; + var index; + _fx2Devices.forEach(function (e, i) { if (e.id === id) { match = e; index = i; } }); + return match ? { key: index, data: match } : null; + } + } + ); + + // currently selected fx2 device + var _fx2Device = null; + var _fx2DeviceId = null; + var _switchEventHandler = null; + + // + // Create a public namespace to hold these values. + // + WinJS.Namespace.define("DeviceList", { + // Class for entries held in the list of devices + DeviceListEntry: DeviceListEntry, + + // The list of Fx2 devices found by the PNP watcher + fx2Devices: { get: function () { return _fx2Devices; } }, + + // The projection for the currently selected Fx2 device + getSelectedFx2Device: function () { + return _fx2Device; + }, + + getSelectedFx2DeviceId: function () { return _fx2DeviceId; }, + + setSelectedDevice: function (id, device) { + + if (_fx2Device !== null) { + DeviceList.events.dispatchEvent("deviceclosing"); + } + + _fx2Device = device; + _fx2DeviceId = id; + }, + + clearSelectedDevice: function () {this.setSelectedDevice(null, null);}, + + // Whether the fx2 watcher is currently active. Retains its + // value across suspend/resume + watcherStarted: false, + + // Method to start the PNP watcher + startFx2Watcher: startFx2Watcher, + + // Method to stop the PNP watcher + stopFx2Watcher: stopFx2Watcher, + + // Helper function to generate a table containing boolean values + createBooleanTable: function (newValues, oldValues, options) { + // helper function to generate the tables used to display bar graph and + // switch state + + var table = document.createElement("table"); + table.border = 1; + table.width = "40%"; + + var head = table.createTHead(); + head = head.insertRow(); + head.insertCell().innerText = options.indexTitle; + head.insertCell().innerText = options.valueTitle; + + var body = table.createTBody(); + var count = newValues.length; + + for (var i = 0; i < count; i += 1) { + + var value = newValues[i]; + var valueName = value ? options.trueValue : options.falseValue; + + var row = body.insertRow(); + row.insertCell().innerText = i; + + var cell = row.insertCell(); + + if (oldValues && oldValues[i] !== newValues[i]) { + var bold = document.createElement("b"); + bold.innerText = valueName; + cell = cell.appendChild(bold); + } + cell.innerText = valueName; + } + return table; + }, + events: new (WinJS.Class.mix(WinJS.Class.define(null), WinJS.Utilities.eventMixin)) + }); + + function initDeviceWatcher() { + // Define the selector to enumerate all of the fx2 device interface class instances + var selector = Windows.Devices.Custom.CustomDevice.getDeviceSelector(Fx2Driver.deviceInterfaceGuid); + + // Create a device watcher to look for instances of the fx2 device interface + _fx2Watcher = Windows.Devices.Enumeration.DeviceInformation.createWatcher( + selector, + ["System.Devices.DeviceInstanceId"] + ); + + // register to know when devices come and go, and when an enumeration ends + _fx2Watcher.addEventListener("added", onFx2Added); + _fx2Watcher.addEventListener("removed", onFx2Removed); + _fx2Watcher.addEventListener("enumerationcompleted", onFx2EnumerationComplete); + } + + function startFx2Watcher() { + WinJS.log && WinJS.log("starting device watcher", "sample", "info"); + // Clear the matched property on each entry that's already in the list + if (!DeviceList.watcherStarted) { + DeviceList.watcherStarted = true; + resumeFx2Watcher(); + } + } + + function suspendFx2Watcher() { + if (DeviceList.watcherStarted) { + _fx2Watcher.stop(); + } + } + + function resumeFx2Watcher() { + if (DeviceList.watcherStarted) { + // Clear the matched property on each entry that's already in the list + _fx2Devices.forEach(function (e) { e.matched = false; }); + _fx2Watcher.start(); + } + } + + function stopFx2Watcher() { + WinJS.log && WinJS.log("stopping fx2 watcher", "sample", "info"); + suspendFx2Watcher(); + DeviceList.watcherStarted = false; + } + + // Event handler for arrival of Fx2 devices. If the device isn't already in the + // list then it's added. If it is already in the list, then the verified property + // is set to true so that endDeviceListUpdate() knows the device is still present + function onFx2Added(devInterface) { + WinJS.log && WinJS.log("onFx2Added: " + devInterface.id, "sample", "info"); + + // Search the device list for a device with a matching interface ID + var match = DeviceListEntry.findInList(devInterface.id); + + // If we found a match then mark it as verified and return + if (match !== null) { + match.data.matched = true; + return; + } + + // Create a new element for this device interface, and queue up the query of its + // device information. + match = new DeviceListEntry(devInterface); + + // Add the new element to the end of the list of devices + _fx2Devices.push(match); + } + + // event handler for removal of an Fx2 device. If the device is in the list, it clears + // the verified property and then purges it from the list by calling endDeviceListUpdate() + function onFx2Removed(devInformation) { + var deviceId = devInformation.id; + WinJS.log && WinJS.log("onFx2Removed: " + deviceId, "sample", "info"); + + // Search the list of devices for one with a matching ID. + var match = DeviceListEntry.findInList(devInformation.id); + if (match !== null) { + // remove the matched item + WinJS.log && WinJS.log("onFx2Removed: " + deviceId, "sample", "info"); + _fx2Devices.splice(match.key, 1); + } + } + + // Event handler for the end of the enumeration triggered when starting the watcher + // This calls endDeviceListUpdate() to purge the device list of any devices which + // are no longer present (their verified property is false) + function onFx2EnumerationComplete(devInformation) { + // Iterate through the list of devices and remove any that hasn't been matched. + // removing the selected entry will automatically trigger its close + _fx2Devices.forEach( + function (entry, index) { + if (!entry.matched) { + _fx2Devices.splice(index, 1); + } + } + ); + } + + // event callback for suspend events. Stops the device watcher + function onSuspend() { + // stop the device watcher when we get suspended. We will restart it on resume, + // which will trigger a new enumeration. + suspendFx2Watcher(); + } + + // event callback for resume events. clears the verified property on all devices + // then restarts the device watcher. + function onResume() { + resumeFx2Watcher(); + } + + // + // Register global handlers for suspend/resume to stop and restart the device + // watcher. + // + Windows.UI.WebUI.WebUIApplication.addEventListener("suspending", onSuspend, false); + Windows.UI.WebUI.WebUIApplication.addEventListener("resuming", onResume, false); + + // + // Initialize the device watcher + // + initDeviceWatcher(); +})(); diff --git a/Samples/CustomCapability/js/js/fx2Driver.js b/Samples/CustomCapability/js/js/fx2Driver.js new file mode 100644 index 0000000000..3c4a3a8207 --- /dev/null +++ b/Samples/CustomCapability/js/js/fx2Driver.js @@ -0,0 +1,66 @@ +//// Copyright (c) Microsoft Corporation. All rights reserved + +(function () { + "use strict"; + + var Custom = Windows.Devices.Custom; + var deviceType = 0x5500; + var functionBase = 0x800; + + var sevenSegmentValues = [ + 0xD7, // 0 + 0x06, // 1 + 0xB3, // 2 + 0xA7, // 3 + 0x66, // 4 + 0xE5, // 5 + 0xF4, // 6 + 0x07, // 7 + 0xF7, // 8 + 0x67, // 9 + ]; + + // + // Define a class to hold entries in the list of devices. + // + WinJS.Namespace.define( + "Fx2Driver", + { + //deviceType: deviceType, + //functionBase: functionBase, + + getSevenSegmentDisplay: new Custom.IOControlCode(deviceType, + functionBase + 7, + Custom.IOControlAccessMode.read, + Custom.IOControlBufferingMethod.buffered), + + setSevenSegmentDisplay: new Custom.IOControlCode(deviceType, + functionBase + 8, + Custom.IOControlAccessMode.write, + Custom.IOControlBufferingMethod.buffered), + + readSwitches: new Custom.IOControlCode(deviceType, + functionBase + 6, + Custom.IOControlAccessMode.read, + Custom.IOControlBufferingMethod.buffered), + + getInterruptMessage: new Custom.IOControlCode(deviceType, + functionBase + 9, + Custom.IOControlAccessMode.read, + Custom.IOControlBufferingMethod.directOutput), + + digitToSevenSegment: function (value) { return sevenSegmentValues[value]; }, + + sevenSegmentToDigit: function (value) { + for (var i = 0; i < sevenSegmentValues.length; i += 1) { + if (sevenSegmentValues[i] === value) { + return i; + } + } + return NaN; + }, + + deviceInterfaceGuid: "573E8C73-0CB4-4471-A1BF-FAB26C31D384" + } + ); +})(); diff --git a/Samples/CustomCapability/js/js/sample-configuration.js b/Samples/CustomCapability/js/js/sample-configuration.js new file mode 100644 index 0000000000..ab53115dda --- /dev/null +++ b/Samples/CustomCapability/js/js/sample-configuration.js @@ -0,0 +1,19 @@ +//// Copyright (c) Microsoft Corporation. All rights reserved + +(function() { + "use strict"; + var sampleTitle = "CustomCapability JS Sample"; + + var scenarios = [ + { url: "/html/Scenario1_MeteringData.html", title: "Connect to an NT Service" }, + { url: "/html/scenario2_deviceConnect.html", title: "Connecting to the Fx2 Device" }, + { url: "/html/scenario3_deviceIO.html", title: "Getting and Setting device properties" }, + { url: "/html/scenario4_deviceEvents.html", title: "Registering for device events" }, + { url: "/html/scenario5_deviceReadWrite.html", title: "Read and Write operations" }, + ]; + + WinJS.Namespace.define("SdkSample", { + sampleTitle: sampleTitle, + scenarios: new WinJS.Binding.List(scenarios) + }); +})(); \ No newline at end of file diff --git a/Samples/CustomCapability/js/js/scenario2_deviceConnect.js b/Samples/CustomCapability/js/js/scenario2_deviceConnect.js new file mode 100644 index 0000000000..f2873765b1 --- /dev/null +++ b/Samples/CustomCapability/js/js/scenario2_deviceConnect.js @@ -0,0 +1,190 @@ +//// Copyright (c) Microsoft Corporation. All rights reserved + +(function () { + "use strict"; + var page = WinJS.UI.Pages.define("/html/scenario2_deviceConnect.html", { + ready: function (element, options) { + document.getElementById("device-connect-devices").addEventListener("selectionchanged", onSelectFx2Device, false); + + document.getElementById("device-connect-start").addEventListener("click", onStartWatcher, false); + document.getElementById("device-connect-stop").addEventListener("click", onStopWatcher, false); + + updateStartStopButtons(); + }, + processed: function (element, options) { + if (DeviceList.fx2Device) { + var match = DeviceList.DeviceListEntry.findInList(DeviceList.fx2Device.id); + + // Select the list element corresponding to the Fx2 device we have open + var listView = document.getElementById("device-connect-devices").winControl; + var selection = listView.selection; + selection.set(match.key); + } + return; + } + }); + + function onStartWatcher() { + document.getElementById("device-connect-start").setAttribute("disabled", "disabled"); + WinJS.log && WinJS.log("starting device watcher", "sample", "status"); + DeviceList.startFx2Watcher(); + updateStartStopButtons(); + } + + function onStopWatcher() { + document.getElementById("device-connect-stop").setAttribute("disabled", ""); + DeviceList.stopFx2Watcher(); + updateStartStopButtons(); + } + + function updateStartStopButtons() { + var started = DeviceList.watcherStarted; + var start = document.getElementById("device-connect-start"); + var stop = document.getElementById("device-connect-stop"); + + var enable = started ? stop : start; + var disable = started ? start : stop; + + enable.removeAttribute("disabled"); + disable.setAttribute("disabled", "disabled"); + } + + // event handler for selected events on the UI list of devices + function onSelectFx2Device(e) { + var x = 0; + var selection = e.target.winControl.selection; + selection.getItems(). + then(function (i) { + var currentlySelectedId = DeviceList.getSelectedFx2DeviceId(); + var newlySelectedId = i.length ? i[0].data.id : null; + + // + // If the ids match there's no work to do. + // + if (currentlySelectedId === newlySelectedId) { + return; + } + + // + // If there's a currently selected item them close it. + // + if (currentlySelectedId) { + closeFx2Device(); + } + + // + // Open the new item + // + if (newlySelectedId) { + e.target.disabled = true; + var p = openFx2DeviceAsync(newlySelectedId); + + p.then( + function() {e.target.disabled = false;} + ); + } + }). + done( + null, + function () { + // An error occurred while trying to select a device. Clear the selection. + e.target.winControl.selection.clear(); + return; + }); + } + + // Called before starting the device watcher, this clears + // the verified flag on every device in the list. + function beginDeviceListUpdate() { + DeviceList.fx2Devices.forEach(function (d) { d.verified = false; }); + } + + // Called after the device watcher's enumeration completes, this + // removes every list entry that hasn't been verified yet. + function endDeviceListUpdate() { + + // Iterate through the list and remove any unverified entry. + DeviceList.fx2Devices.forEach( + function (element, index) { + if (!element.verified) { + DeviceList.fx2Devices.splice(index, 1); + } + } + ); + } + + // Iterates across the list of Fx2 devices until the match function returns true + function findDeviceInfo(matchFunction) { + var match = null; + DeviceList.fx2Devices.forEach(function (e, index) { + if (matchFunction(e)) { + match = e; + } + }); + return match; + } + + // creates an instance of the Samples.Devices.Fx2.Fx2Device class for the specified + // device + function openFx2DeviceAsync(id) { + + DeviceList.clearSelectedDevice(); + + document.getElementById("device-connect-output").innerHTML = ""; + + // Open a handle to the fx2 device. + var p = + Windows.Devices.Custom.CustomDevice.fromIdAsync( + id, + Windows.Devices.Custom.DeviceAccessMode.readWrite, + Windows.Devices.Custom.DeviceSharingMode.exclusive + ); + + p = p.then( + function(device) + { + DeviceList.setSelectedDevice(id, device); + WinJS.log && WinJS.log("Fx2 " + id + " opened", "sample", "status"); + }, + function(error) + { + WinJS.log && WinJS.log("Error opening Fx2 device @" + id + ": " + error, "sample", "error"); + diagnoseConnectionError(id, error); + throw error; + } + ); + + return p; + } + + // Closes the currently opened device + function closeFx2Device() { + + if (DeviceList.fx2Device !== null) { + // drop the reference to the current device, so it can be closed + WinJS.log && WinJS.log("Fx2 " + DeviceList.fx2Device.id + " closed", "sample", "status"); + DeviceList.fx2Device = null; + } + + return; + } + + // Diagnostic routine that analyzes why creating the Fx2Device failed. This looks at + // the device's properties and matches them to the expected values to report common + // developer errors in the device metadata or INF file. + // + // A real app might use this to report error data back to the developer, but none of + // the errors reported here are something the user has done wrong or can fix, so they + // shouldn't be reported to a real user. + function diagnoseConnectionError(id, error) { + + WinJS.log && WinJS.log("Unable to open Fx2 device " + id + ".\n" + + error + "\nLikely causes are:\n" + + "1. Device metadata does not grant custom privileged access to any apps\n" + + "2. Device metadata and the app package have a mismatch on the app name and/or publisher ID\n" + + "3. The device interface was not properly marked as restricted by the driver\n" + + "4. The device is no longer connected\n", + "sample", "error"); + + } +})(); diff --git a/Samples/CustomCapability/js/js/scenario3_deviceIO.js b/Samples/CustomCapability/js/js/scenario3_deviceIO.js new file mode 100644 index 0000000000..54d3bb5b27 --- /dev/null +++ b/Samples/CustomCapability/js/js/scenario3_deviceIO.js @@ -0,0 +1,81 @@ +//// Copyright (c) Microsoft Corporation. All rights reserved + +(function () { + "use strict"; + var page = WinJS.UI.Pages.define("/html/scenario3_deviceIO.html", { + ready: function (element, options) { + document.getElementById("device-properties-get").addEventListener("click", onGetSegmentValue, false); + document.getElementById("device-properties-set").addEventListener("click", onSetSegmentValue, false); + } + }); + + function onGetSegmentValue() { + + var fx2Device = DeviceList.getSelectedFx2Device(); + + if (!fx2Device) { + WinJS.log && WinJS.log("Fx2 device not connected or accessible", "sample", "error"); + return; + } + + var outputBuffer = new Windows.Storage.Streams.Buffer(1); + + fx2Device.sendIOControlAsync( + Fx2Driver.getSevenSegmentDisplay, + null, + outputBuffer + ). + then( + function (bytesRead) { + var reader = Windows.Storage.Streams.DataReader.fromBuffer(outputBuffer); + var segment = reader.readByte(); + segment = Fx2Driver.sevenSegmentToDigit(segment); + + if (isNaN(segment)) { + WinJS.log && WinJS.log("The segment display value is not yet initialized.", "sample", "status"); + } else { + WinJS.log && WinJS.log("The segment display value is " + segment, "sample", "status"); + } + }, + + function (error) { + WinJS.log && WinJS.log(error, "sample", "error"); + } + ); + } + + // Sets the segment display value on the Fx2 Device + function onSetSegmentValue() { + + var fx2Device = DeviceList.getSelectedFx2Device(); + + if (!fx2Device) { + WinJS.log && WinJS.log("Fx2 device not connected or accessible", "sample", "error"); + return; + } + + var segmentSelector = document.getElementById("device-properties-segmentInput"); + // Get the selected value to be set on the device + var val = segmentSelector.options[segmentSelector.selectedIndex].value; + + var writer = new Windows.Storage.Streams.DataWriter(); + writer.writeByte(Fx2Driver.digitToSevenSegment(val)); + var inputBuffer = writer.detachBuffer(); + + fx2Device.sendIOControlAsync( + Fx2Driver.setSevenSegmentDisplay, + inputBuffer, + null + ). + then( + function (bytesRead) { + WinJS.log && WinJS.log("The segment display is set to " + val, "sample", "status"); + }, + + function (error) { + WinJS.log && WinJS.log(error, "sample", "error"); + } + ); + } + +})(); diff --git a/Samples/CustomCapability/js/js/scenario4_deviceEvents.js b/Samples/CustomCapability/js/js/scenario4_deviceEvents.js new file mode 100644 index 0000000000..740a1bf235 --- /dev/null +++ b/Samples/CustomCapability/js/js/scenario4_deviceEvents.js @@ -0,0 +1,201 @@ +//// Copyright (c) Microsoft Corporation. All rights reserved + +(function () { + "use strict"; + + // Has the user enabled switch change events? + var currentPromise = null; + + // The previous switch values - saved to allow the switch changes to be + // bolded. + var previousSwitchValues = null; + + var page = WinJS.UI.Pages.define("/html/scenario4_deviceEvents.html", { + ready: function (element, options) { + document.getElementById("device-events-get").addEventListener("click", onGetSwitchState, false); + document.getElementById("device-events-begin").addEventListener("click", onDeviceEventsBegin, false); + document.getElementById("device-events-cancel").addEventListener("click", onDeviceEventsCancel, false); + }, + processed: function () { + clearSwitchStateTable(); + updateRegisterButton(); + }, + unload: function () { + if (currentPromise) { + currentPromise.cancel(); + } + } + }); + + function updateRegisterButton() { + var startButton = document.getElementById("device-events-begin"); + var cancelButton = document.getElementById("device-events-cancel"); + + if (!startButton || !cancelButton) { + return; + } + + if (currentPromise) { + startButton.setAttribute("disabled", currentPromise ? "disabled" : ""); + cancelButton.removeAttribute("disabled"); + } else { + startButton.removeAttribute("disabled"); + cancelButton.setAttribute("disabled", !currentPromise ? "disabled" : ""); + } + } + + // Gets the current switch state of the Fx2 Device dip switches + function onGetSwitchState() { + var fx2Device = DeviceList.getSelectedFx2Device(); + + if (!fx2Device) { + WinJS.log && WinJS.log("Fx2 device not connected or accessible", "sample", "error"); + return; + } + + var outputBuffer = new Windows.Storage.Streams.Buffer(1); + + fx2Device.sendIOControlAsync( + Fx2Driver.readSwitches, + null, + outputBuffer + ). + done( + function (bytesRead) { + + var reader = Windows.Storage.Streams.DataReader.fromBuffer(outputBuffer); + var switchState = reader.readByte(); + + var switchStateArray = createSwitchStateArray(switchState); + + updateSwitchStateTable(switchStateArray); + }, + + function (error) { + WinJS.log && WinJS.log(error, "sample", "error"); + } + ); + } + + function onDeviceEventsBegin() { + var fx2Device = DeviceList.getSelectedFx2Device(); + + if (!fx2Device) { + WinJS.log && WinJS.log("Fx2 device not connected or accessible", "sample", "error"); + return; + } + + if (!currentPromise) { + startInterruptMessageWorker(fx2Device); + } + + updateRegisterButton(); + } + + function onDeviceEventsCancel() { + var fx2Device = DeviceList.getSelectedFx2Device(); + + if (!fx2Device) { + WinJS.log && WinJS.log("Fx2 device not connected or accessible", "sample", "error"); + return; + } + + if (currentPromise) { + currentPromise.cancel(); + } + + updateRegisterButton(); + } + + function startInterruptMessageWorker(fx2Device) { + + var buffer = new Windows.Storage.Streams.Buffer(1); + + interruptMessageWorker(fx2Device, buffer); + } + + function interruptMessageWorker(fx2Device, switchMessageBuffer) { + currentPromise = fx2Device.sendIOControlAsync( + Fx2Driver.getInterruptMessage, + null, + switchMessageBuffer + ). + then( + function (bytesRead) { + if (bytesRead === 0) { + throw new WinJS.ErrorFromName("NoData"); + } + + var reader = Windows.Storage.Streams.DataReader.fromBuffer(switchMessageBuffer); + var switchState = reader.readByte(); + var switchStateArray = createSwitchStateArray(switchState); + + updateSwitchStateTable(switchStateArray); + + // Start the next iteration of the worker. + interruptMessageWorker(fx2Device, switchMessageBuffer); + }, + + function (error) { + /// + if (error.name === "NoData") { + WinJS.log && WinJS.log("Fx2 device returned 0 byte interrupt message. Stopping", + "sample", + "error"); + } + else if (error.name === "Canceled") { + WinJS.log && WinJS.log("Pending getInterruptMessage IO control cancelled", "sample", "status"); + } + else { + WinJS.log && WinJS.log("Error accessing Fx2 device: " + error, "sample", "error"); + } + + currentPromise = null; + + clearSwitchStateTable(); + updateRegisterButton(); + } + ); + } + + function createSwitchStateArray(value) { + + var switchStateArray = new Array(8); + + for (var i = 0; i < switchStateArray.length; i += 1) { + switchStateArray[i] = (value & (1 << i)) !== 0; + } + + return switchStateArray; + } + + + function clearSwitchStateTable() { + var table = document.getElementById("device-events-switches"); + + if (table) { + table.innerHTML = ""; + } + } + + function updateSwitchStateTable(switchStateArray) { + + var table = DeviceList.createBooleanTable( + switchStateArray, + previousSwitchValues, + { + indexTitle: "Switch Number", + valueTitle: "Switch State", + trueValue: "off", + falseValue: "on" + } + ); + var output = document.getElementById("device-events-switches"); + if (output.children.length > 0) { + output.removeChild(output.children[0]); + } + output.insertBefore(table); + + previousSwitchValues = switchStateArray; + } +})(); diff --git a/Samples/CustomCapability/js/js/scenario5_deviceReadWrite.js b/Samples/CustomCapability/js/js/scenario5_deviceReadWrite.js new file mode 100644 index 0000000000..0efe3d2c8b --- /dev/null +++ b/Samples/CustomCapability/js/js/scenario5_deviceReadWrite.js @@ -0,0 +1,91 @@ +//// Copyright (c) Microsoft Corporation. All rights reserved + +(function () { + "use strict"; + + var readCounter = 0; + var writeCounter = 0; + + var page = WinJS.UI.Pages.define("/html/scenario5_deviceReadWrite.html", { + ready: function (element, options) { + document.getElementById("device-readwrite-read").addEventListener("click", onReadBlock, false); + document.getElementById("device-readwrite-write").addEventListener("click", onWriteBlock, false); + } + }); + + function logMessage(msg) { + var output = document.getElementById("device-readwrite-output"); + output.innerHTML = "

" + msg + "

" + output.innerHTML; + WinJS.log && WinJS.log(msg, "sample"); + } + + function onReadBlock() { + + var fx2Device = DeviceList.getSelectedFx2Device(); + + if (!fx2Device) { + WinJS.log && WinJS.log("Fx2 device not connected or accessible", "sample", "error"); + return; + } + + var button = document.getElementById("device-readwrite-read"); + button.disabled = true; + + var inputStream = fx2Device.inputStream; + var dataReader = new Windows.Storage.Streams.DataReader(inputStream); + + var counter = readCounter++; + + logMessage("Read " + counter + " begin"); + + // Read up to 64 bytes into the data reader + dataReader.loadAsync(64). + then( + function (operation) { + var message = dataReader.readString(dataReader.unconsumedBufferLength); + logMessage("Read " + counter + " end: " + message); + button.disabled = false; + }, + function (error) { + WinJS.log && WinJS.log(error, "sample", "error"); + button.disabled = false; + } + ); + } + + function onWriteBlock() { + + var fx2Device = DeviceList.getSelectedFx2Device(); + + if (!fx2Device) { + WinJS.log && WinJS.log("Fx2 device not connected or accessible", "sample", "error"); + return; + } + + var button = document.getElementById("device-readwrite-write"); + button.disabled = true; + + var dataWriter = new Windows.Storage.Streams.DataWriter(fx2Device.outputStream); + + var counter = writeCounter++; + + var message = "This is message " + counter; + + dataWriter.writeString(message); + + logMessage("Write " + counter + " begin: " + message); + + dataWriter.storeAsync(). + then( + function (bytesWritten) { + logMessage("Write " + counter + " end: " + bytesWritten + " bytes written"); + button.disabled = false; + }, + function (error) { + WinJS.log && WinJS.log(error, "sample", "error"); + button.disabled = false; + } + ); + } + +})(); diff --git a/Samples/CustomCapability/js/package.appxmanifest b/Samples/CustomCapability/js/package.appxmanifest new file mode 100644 index 0000000000..1c5dd4f907 --- /dev/null +++ b/Samples/CustomCapability/js/package.appxmanifest @@ -0,0 +1,43 @@ + + + + + + CustomCapability + Microsoft Corporation + Assets\StoreLogo-sdk.png + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/CustomCapability/cpp/Scenario1_MeteringData.xaml b/Samples/CustomCapability/shared/Scenario1_MeteringData.xaml similarity index 100% rename from Samples/CustomCapability/cpp/Scenario1_MeteringData.xaml rename to Samples/CustomCapability/shared/Scenario1_MeteringData.xaml diff --git a/Samples/CustomCapability/cpp/Scenario2_DeviceConnect.xaml b/Samples/CustomCapability/shared/Scenario2_DeviceConnect.xaml similarity index 100% rename from Samples/CustomCapability/cpp/Scenario2_DeviceConnect.xaml rename to Samples/CustomCapability/shared/Scenario2_DeviceConnect.xaml diff --git a/Samples/CustomCapability/cpp/Scenario3_DeviceIO.xaml b/Samples/CustomCapability/shared/Scenario3_DeviceIO.xaml similarity index 100% rename from Samples/CustomCapability/cpp/Scenario3_DeviceIO.xaml rename to Samples/CustomCapability/shared/Scenario3_DeviceIO.xaml diff --git a/Samples/CustomCapability/cpp/Scenario4_DeviceEvents.xaml b/Samples/CustomCapability/shared/Scenario4_DeviceEvents.xaml similarity index 100% rename from Samples/CustomCapability/cpp/Scenario4_DeviceEvents.xaml rename to Samples/CustomCapability/shared/Scenario4_DeviceEvents.xaml diff --git a/Samples/CustomCapability/cpp/Scenario5_DeviceReadWrite.xaml b/Samples/CustomCapability/shared/Scenario5_DeviceReadWrite.xaml similarity index 100% rename from Samples/CustomCapability/cpp/Scenario5_DeviceReadWrite.xaml rename to Samples/CustomCapability/shared/Scenario5_DeviceReadWrite.xaml diff --git a/Samples/Ink/js/css/scenario1.css b/Samples/Ink/js/css/scenario1.css index 2fef18eac1..9f9dfaba6a 100644 --- a/Samples/Ink/js/css/scenario1.css +++ b/Samples/Ink/js/css/scenario1.css @@ -44,6 +44,7 @@ body height: 100%; overflow: scroll; z-index: 0; + pen-action: none; } div.rectangle diff --git a/Samples/Logging/README.md b/Samples/Logging/README.md index 2c004aedac..090d680989 100644 --- a/Samples/Logging/README.md +++ b/Samples/Logging/README.md @@ -51,6 +51,76 @@ To obtain information about Windows 10 development, go to the [Windows Dev Cente 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) +## How to capture logging output + +You can collect the events generated by the logging classes with xperf or another +ETL controller tool. To collect these events in an ETL file, first start the capture +session by running the command + + xperf -start MySession -f MyFile.etl -on Id + +where the Id is given below. + +Next, run the sample and run the appropriate scenario. + +When done, run the command + + xperf -stop MySession + +After collecting the ETL file, you can decode the trace using xperf, wpa, +or tracerpt. For example, to decode MyFile.etl with tracerpt, run the command + + tracerpt MyFile.etl + +This generates the file `dumpfile.xml`. + +Note that decoding TraceLogging events requires Windows 10. Earlier versions +of Windows can only reliably decode the simple (manifest-based) events. + +### Windows 8.1 style events + +If you use the `LoggingChannel` class's one-parameter constructor, +the result is a Windows 8.1-style logging channel. + +* The channel's ETW Id is `4bd2826e-54a1-4ba9-bf63-92b73ea1ac4a` +* The channel's ETW Name is `Microsoft-Windows-Diagnostics-LoggingChannel` +* A LoggingChannelName field containing the Channel Name is automatically added to each event. +* Simple events will be written using manifested-event encoding. + +Therefore, you would use the command line + + xperf -start MySession -f MyFile.etl -on 4bd2826e-54a1-4ba9-bf63-92b73ea1ac4a + +to start capturing events with xperf. + +Note that complex events can be written even when using Windows 8.1 mode. +Complex events always use TraceLogging encoding. + +The 1-parameter constructor is marked as obsolete to ensure that +developers are aware of the changes in semantics. The Windows 10 semantics +are useful because they enable use of the ETW Provider Id for event +filtering. + +### Windows 10 style events + +If you use the `LoggingChannel` class's two-parameter or three-parameter constructor, +the result is a Windows 10-style logging channel. + +* The channel's ETW Id is specified by the third constructor parameter, if present; otherwise it is generated from the Channel Name by the same hashing algorithm as the .NET EventSource class. +* The channel's ETW Name is specified by the first constructor parameter. + +In the sample, the channel name is "SampleProvider", which hashes to `eff1e128-4903-5093-096a-bdc29b38456f`. + +Therefore, you would use the command line + + xperf -start MySession -f MyFile.etl -on eff1e128-4903-5093-096a-bdc29b38456f + +to start capturing events with xperf. + +xperf version 10.0.16299 and higher support specifying the channel name with a leading asterisk: + + xperf -start MySession -f MyFile.etl -on *SampleProvider + ## Related topics ### Samples @@ -63,6 +133,7 @@ To obtain information about Microsoft Visual Studio and the tools for developing [LoggingActivity](https://msdn.microsoft.com/library/windows/apps/windows.foundation.diagnostics.loggingactivity.aspx) [LoggingSession](https://msdn.microsoft.com/library/windows/apps/windows.foundation.diagnostics.loggingsession.aspx) [FileLoggingSession](https://msdn.microsoft.com/library/windows/apps/windows.foundation.diagnostics.fileloggingsession.aspx) +[Windows Performance Toolkit](https://docs.microsoft.com/en-us/windows-hardware/test/wpt/index) ## System requirements diff --git a/Samples/Logging/cpp/LoggingChannelScenario.cpp b/Samples/Logging/cpp/LoggingChannelScenario.cpp index 3ef0662074..ecfaa3d0d7 100644 --- a/Samples/Logging/cpp/LoggingChannelScenario.cpp +++ b/Samples/Logging/cpp/LoggingChannelScenario.cpp @@ -12,118 +12,41 @@ using namespace SDKTemplate; using namespace Windows::Foundation; using namespace Windows::Foundation::Diagnostics; +/// +/// Construct a LoggingChannel with the Windows 8.1 constructor, then +/// use the LoggingChannel for the scenario. +/// See this sample's README for further discussion. +/// void LoggingChannelScenario::LogWithWin81Constructor() { /* - You can collect the events generated by this method with xperf or another - ETL controller tool. To collect these events in an ETL file: - - xperf -start MySession -f MyFile.etl -on 4bd2826e-54a1-4ba9-bf63-92b73ea1ac4a - (run the sample and click the "Windows 8.1 behavior" button) - xperf -stop MySession - - After collecting the ETL file, you can decode the trace using xperf, wpa, - or tracerpt. For example, to decode MyFile.etl with tracerpt: - - tracerpt MyFile.etl - (generates dumpfile.xml) - - Note that decoding TraceLogging events requires Windows 10. Earlier versions - of Windows can only reliably decode the simple (manifest-based) events. - */ - - /* - If a LoggingChannel is created using the 1-parameter constructor, it - will use Windows 8.1 semantics: - - - The channel's ETW Id is always "4bd2826e-54a1-4ba9-bf63-92b73ea1ac4a". - - The channel's ETW Name is is always - "Microsoft-Windows-Diagnostics-LoggingChannel". - - A LoggingChannelName field containing the Channel Name is automatically - added to each event. - - Simple events will be written using manifested-event encoding. - - Note that complex events can be written even when using Windows 8.1 mode. - Complex events always use TraceLogging encoding. - - The 1-parameter constructor is marked as obsolete to ensure that - developers are aware of the changes in semantics. The Windows 10 semantics - are useful because they enable use of the ETW Provider Id for event - filtering. + If a LoggingChannel is used as a local or member variable, it should + be closed (disposed) when no longer needed. Use the C++/CX "delete" + operator to close the object. */ - { - /* - If a LoggingChannel is used as a local or member variable, it should - be closed (disposed) when no longer needed. C++/CX will do this - automatically when the channel variable goes out of scope. - */ - - // The 1-parameter constructor creates a channel with Windows 8.1 semantics. + // The 1-parameter constructor creates a channel with Windows 8.1 semantics. #pragma warning(suppress: 4973) // Disable warning for use of obsolete LoggingChannel constructor. - auto channel = ref new LoggingChannel("SampleProvider"); + auto channel = ref new LoggingChannel("SampleProvider"); - DemonstrateLogging(channel); - } + DemonstrateLogging(channel); + + delete channel; } /// /// Construct a LoggingChannel with the Windows 10 constructor, then /// use the LoggingChannel for the scenario. Also show the use cases /// for the two Windows 10 constructors. +// See this sample's README for further discussion. /// void LoggingChannelScenario::LogWithWin10Constructor() { - /* - You can collect the events generated by this method with xperf or another - ETL controller tool. To collect these events in an ETL file: - - xperf -start MySession -f MyFile.etl -on eff1e128-4903-5093-096a-bdc29b38456f - (run the sample and click the "Windows 10 behavior" button) - xperf -stop MySession - - After collecting the ETL file, you can decode the trace using xperf, wpa, - or tracerpt. For example, to decode MyFile.etl with tracerpt: - - tracerpt MyFile.etl - (generates dumpfile.xml) - - Note that decoding TraceLogging events requires Windows 10. Earlier versions - of Windows can only reliably decode the simple (manifest-based) events. - */ - - /* - If a LoggingChannel is created using the 2-parameter or 3-parameter - constructors, it will use Windows 10 semantics: - - - The channel's ETW Provider Id can be controlled by the developer. - - The channel's ETW Name is the same as the Channel Name. - - The LoggingChannelName field is not automatically added to each event. - - All events will be written using TraceLogging encoding. - - The 1-parameter constructor is marked as obsolete to ensure that - developers are aware of the changes in semantics. The Windows 10 semantics - are useful because they enable use of the ETW Provider Id for event - filtering. - - The 2-parameter constructor accepts a channel name and channel options. - If the options parameter is null, default options are used. At present, - the only available option is the channel's group GUID. When using the - 2-parameter constructor, the channel's ETW Id is determined by hashing - the channel name. (The hashing algorithm is the same as the one used by - the .NET EventSource class.) - - The 3-parameter constructor accepts channel name, channel options, and - the ETW Id. This constructor should be used when you need your channel - to use a specific ETW Id and cannot use the default hashed channel name - as your ETW Id. - */ - { /* If a LoggingChannel is used as a local or member variable, it should - be closed (disposed) when no longer needed. C++/CX will do this - automatically when the channel variable goes out of scope. + be closed (disposed) when no longer needed. Use the C++/CX "delete" + operator to close the object. */ // The 2-parameter constructor creates a channel with Windows 10 semantics. @@ -133,8 +56,9 @@ void LoggingChannelScenario::LogWithWin10Constructor() // The Id is generated by hashing the string "SampleProvider". // channel.Id == eff1e128-4903-5093-096a-bdc29b38456f - DemonstrateLogging(channel); + + delete channel; } /* @@ -150,6 +74,8 @@ void LoggingChannelScenario::LogWithWin10Constructor() ref new LoggingChannelOptions(g)); // Join a provider group // The Id is generated by hashing the string "SampleProvider". // channel.Id == eff1e128-4903-5093-096a-bdc29b38456f + + delete channel; } { @@ -161,6 +87,8 @@ void LoggingChannelScenario::LogWithWin10Constructor() nullptr, g); // channel.Id == 2e0582f3-d1b6-516a-9de3-9fd79ef952f8 + + delete channel; } } diff --git a/Samples/Logging/cs/LoggingChannelScenario.cs b/Samples/Logging/cs/LoggingChannelScenario.cs index 25e05a0a9c..991370b766 100644 --- a/Samples/Logging/cs/LoggingChannelScenario.cs +++ b/Samples/Logging/cs/LoggingChannelScenario.cs @@ -17,47 +17,10 @@ internal class LoggingChannelScenario /// /// Construct a LoggingChannel with the Windows 8.1 constructor, then /// use the LoggingChannel for the scenario. + /// See this sample's README for further discussion. /// public void LogWithWin81Constructor() { - /* - You can collect the events generated by this method with xperf or another - ETL controller tool. To collect these events in an ETL file: - - xperf -start MySession -f MyFile.etl -on 4bd2826e-54a1-4ba9-bf63-92b73ea1ac4a - (run the sample and click the "Windows 8.1 behavior" button) - xperf -stop MySession - - After collecting the ETL file, you can decode the trace using xperf, wpa, - or tracerpt. For example, to decode MyFile.etl with tracerpt: - - tracerpt MyFile.etl - (generates dumpfile.xml) - - Note that decoding TraceLogging events requires Windows 10. Earlier versions - of Windows can only reliably decode the simple (manifest-based) events. - */ - - /* - If a LoggingChannel is created using the 1-parameter constructor, it - will use Windows 8.1 semantics: - - - The channel's ETW Id is always "4bd2826e-54a1-4ba9-bf63-92b73ea1ac4a". - - The channel's ETW Name is is always - "Microsoft-Windows-Diagnostics-LoggingChannel". - - A LoggingChannelName field containing the Channel Name is automatically - added to each event. - - Simple events will be written using manifested-event encoding. - - Note that complex events can be written even when using Windows 8.1 mode. - Complex events always use TraceLogging encoding. - - The 1-parameter constructor is marked as obsolete to ensure that - developers are aware of the changes in semantics. The Windows 10 semantics - are useful because they enable use of the ETW Provider Id for event - filtering. - */ - /* If a LoggingChannel is used as a local or member variable, it should be closed (disposed) when no longer needed. C# will do this automatically @@ -80,54 +43,10 @@ developers are aware of the changes in semantics. The Windows 10 semantics /// Construct a LoggingChannel with the Windows 10 constructor, then /// use the LoggingChannel for the scenario. Also show the use cases /// for the two Windows 10 constructors. + /// See this sample's README for further discussion. /// public void LogWithWin10Constructor() { - /* - You can collect the events generated by this method with xperf or another - ETL controller tool. To collect these events in an ETL file: - - xperf -start MySession -f MyFile.etl -on eff1e128-4903-5093-096a-bdc29b38456f - (run the sample and click the "Windows 10 behavior" button) - xperf -stop MySession - - After collecting the ETL file, you can decode the trace using xperf, wpa, - or tracerpt. For example, to decode MyFile.etl with tracerpt: - - tracerpt MyFile.etl - (generates dumpfile.xml) - - Note that decoding TraceLogging events requires Windows 10. Earlier versions - of Windows can only reliably decode the simple (manifest-based) events. - */ - - /* - If a LoggingChannel is created using the 2-parameter or 3-parameter - constructors, it will use Windows 10 semantics: - - - The channel's ETW Provider Id can be controlled by the developer. - - The channel's ETW Name is the same as the Channel Name. - - The LoggingChannelName field is not automatically added to each event. - - All events will be written using TraceLogging encoding. - - The 1-parameter constructor is marked as obsolete to ensure that - developers are aware of the changes in semantics. The Windows 10 semantics - are useful because they enable use of the ETW Provider Id for event - filtering. - - The 2-parameter constructor accepts a channel name and channel options. - If the options parameter is null, default options are used. At present, - the only available option is the channel's group GUID. When using the - 2-parameter constructor, the channel's ETW Id is determined by hashing - the channel name. (The hashing algorithm is the same as the one used by - the .NET EventSource class.) - - The 3-parameter constructor accepts channel name, channel options, and - the ETW Id. This constructor should be used when you need your channel - to use a specific ETW Id and cannot use the default hashed channel name - as your ETW Id. - */ - /* If a LoggingChannel is used as a local or member variable, it should be closed (disposed) when no longer needed. C# will do this automatically diff --git a/Samples/Logging/js/js/loggingChannelScenario.js b/Samples/Logging/js/js/loggingChannelScenario.js index ba088edd69..f6503334e8 100644 --- a/Samples/Logging/js/js/loggingChannelScenario.js +++ b/Samples/Logging/js/js/loggingChannelScenario.js @@ -15,46 +15,9 @@ /// /// Construct a LoggingChannel with the Windows 8.1 constructor, then /// use the LoggingChannel for the scenario. + /// See this sample's README for further discussion. /// logWithWin81Constructor: function () { - /* - You can collect the events generated by this method with xperf or another - ETL controller tool. To collect these events in an ETL file: - - xperf -start MySession -f MyFile.etl -on 4bd2826e-54a1-4ba9-bf63-92b73ea1ac4a - (run the sample and click the "Windows 8.1 behavior" button) - xperf -stop MySession - - After collecting the ETL file, you can decode the trace using xperf, wpa, - or tracerpt. For example, to decode MyFile.etl with tracerpt: - - tracerpt MyFile.etl - (generates dumpfile.xml) - - Note that decoding TraceLogging events requires Windows 10. Earlier versions - of Windows can only reliably decode the simple (manifest-based) events. - */ - - /* - If a LoggingChannel is created using the 1-parameter constructor, it - will use Windows 8.1 semantics: - - - The channel's ETW Id is always "4bd2826e-54a1-4ba9-bf63-92b73ea1ac4a". - - The channel's ETW Name is is always - "Microsoft-Windows-Diagnostics-LoggingChannel". - - A LoggingChannelName field containing the Channel Name is automatically - added to each event. - - Simple events will be written using manifested-event encoding. - - Note that complex events can be written even when using Windows 8.1 mode. - Complex events always use TraceLogging encoding. - - The 1-parameter constructor is marked as obsolete to ensure that - developers are aware of the changes in semantics. The Windows 10 semantics - are useful because they enable use of the ETW Provider Id for event - filtering. - */ - /* If a LoggingChannel is used as a local or member variable, it should be closed (disposed) when no longer needed. @@ -76,53 +39,9 @@ /// Construct a LoggingChannel with the Windows 10 constructor, then /// use the LoggingChannel for the scenario. Also show the use cases /// for the two Windows 10 constructors. + /// See this sample's README for further discussion. /// logWithWin10Constructor: function () { - /* - You can collect the events generated by this method with xperf or another - ETL controller tool. To collect these events in an ETL file: - - xperf -start MySession -f MyFile.etl -on eff1e128-4903-5093-096a-bdc29b38456f - (run the sample and click the "Windows 10 behavior" button) - xperf -stop MySession - - After collecting the ETL file, you can decode the trace using xperf, wpa, - or tracerpt. For example, to decode MyFile.etl with tracerpt: - - tracerpt MyFile.etl - (generates dumpfile.xml) - - Note that decoding TraceLogging events requires Windows 10. Earlier versions - of Windows can only reliably decode the simple (manifest-based) events. - */ - - /* - If a LoggingChannel is created using the 2-parameter or 3-parameter - constructors, it will use Windows 10 semantics: - - - The channel's ETW Provider Id can be controlled by the developer. - - The channel's ETW Name is the same as the Channel Name. - - The LoggingChannelName field is not automatically added to each event. - - All events will be written using TraceLogging encoding. - - The 1-parameter constructor is marked as obsolete to ensure that - developers are aware of the changes in semantics. The Windows 10 semantics - are useful because they enable use of the ETW Provider Id for event - filtering. - - The 2-parameter constructor accepts a channel name and channel options. - If the options parameter is null, default options are used. At present, - the only available option is the channel's group GUID. When using the - 2-parameter constructor, the channel's ETW Id is determined by hashing - the channel name. (The hashing algorithm is the same as the one used by - the .NET EventSource class.) - - The 3-parameter constructor accepts channel name, channel options, and - the ETW Id. This constructor should be used when you need your channel - to use a specific ETW Id and cannot use the default hashed channel name - as your ETW Id. - */ - /* If a LoggingChannel is used as a local or member variable, it should be closed (disposed) when no longer needed. diff --git a/Samples/MixedRealityModel/README.md b/Samples/MixedRealityModel/README.md new file mode 100644 index 0000000000..0aa025f817 --- /dev/null +++ b/Samples/MixedRealityModel/README.md @@ -0,0 +1,69 @@ + + +# Mixed Reality Model sample + +Shows how you to place 3D content using Secondary Tiles by defining a mixed reality model at creation time. + +Specifically, this sample shows how to: + +- **Create a 3D app launcher:** Define a 3D launcher asset in the app manifest to override the default 2D launcher for your app. +- **Create a 3D Secondary Tile:** Place 3D models from your app into the Windows Mixed Reality home. + +**Note** The Windows universal samples require Visual Studio 2017 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) + +## Related topics + +### Samples + +[SecondaryTiles](../SecondaryTiles) + +### Reference + +[TileMixedRealityModel](https://docs.microsoft.com/uwp/api/windows.ui.startscreen.tilemixedrealitymodel) + +[SecondaryTile](https://docs.microsoft.com/uwp/api/windows.ui.startscreen.secondarytile) + +### Conceptual + +[Implementing 3D app launchers](https://developer.microsoft.com/windows/mixed-reality/implementing_3d_app_launchers) + +[Implementing 3D deep links for your app in the Windows Mixed Reality home](https://developer.microsoft.com/windows/mixed-reality/implementing_3d_deep_links_for_your_app_in_the_windows_mixed_reality_home) + +[3D app launcher design guidance](https://developer.microsoft.com/windows/mixed-reality/3d_app_launcher_design_guidance) + +[Creating 3D models for use in the Windows Mixed Reality home](https://developer.microsoft.com/windows/mixed-reality/creating_3d_models_for_use_in_the_windows_mixed_reality_home) + + +## System requirements + +**Client:** Windows 10 Fall Creators Update + +**Server:** Not supported + +**Phone:** 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 2017 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 selectDebug > Start Without Debugging. diff --git a/Samples/MixedRealityModel/cs/MixedRealityModel.csproj b/Samples/MixedRealityModel/cs/MixedRealityModel.csproj new file mode 100644 index 0000000000..9ec4bcdcee --- /dev/null +++ b/Samples/MixedRealityModel/cs/MixedRealityModel.csproj @@ -0,0 +1,181 @@ + + + + + Debug + x86 + {00007670-DFFC-5059-A04E-7C6A7776C007} + AppContainerExe + Properties + MixedRealityModel + MixedRealityModel + en-US + UAP + 10.0.16299.0 + 10.0.16299.0 + 15 + true + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + win10-arm;win10-arm-aot;win10-x86;win10-x86-aot;win10-x64;win10-x64-aot + + + 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 + + + + Scenario1_CreateTile.xaml + + + + + Designer + + + + + App.xaml + MSBuild:Compile + Designer + + + MainPage.xaml + MSBuild:Compile + Designer + + + 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.0.2 + + + + + glb\microsoft-logo.glb + + + + 15.0 + + + + \ No newline at end of file diff --git a/Samples/MixedRealityModel/cs/MixedRealityModel.sln b/Samples/MixedRealityModel/cs/MixedRealityModel.sln new file mode 100644 index 0000000000..3cc67c5cd5 --- /dev/null +++ b/Samples/MixedRealityModel/cs/MixedRealityModel.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27004.2009 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MixedRealityModel", "MixedRealityModel.csproj", "{00007670-DFFC-5059-A04E-7C6A7776C007}" +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 + {00007670-DFFC-5059-A04E-7C6A7776C007}.Debug|ARM.ActiveCfg = Debug|ARM + {00007670-DFFC-5059-A04E-7C6A7776C007}.Debug|ARM.Build.0 = Debug|ARM + {00007670-DFFC-5059-A04E-7C6A7776C007}.Debug|ARM.Deploy.0 = Debug|ARM + {00007670-DFFC-5059-A04E-7C6A7776C007}.Debug|x64.ActiveCfg = Debug|x64 + {00007670-DFFC-5059-A04E-7C6A7776C007}.Debug|x64.Build.0 = Debug|x64 + {00007670-DFFC-5059-A04E-7C6A7776C007}.Debug|x64.Deploy.0 = Debug|x64 + {00007670-DFFC-5059-A04E-7C6A7776C007}.Debug|x86.ActiveCfg = Debug|x86 + {00007670-DFFC-5059-A04E-7C6A7776C007}.Debug|x86.Build.0 = Debug|x86 + {00007670-DFFC-5059-A04E-7C6A7776C007}.Debug|x86.Deploy.0 = Debug|x86 + {00007670-DFFC-5059-A04E-7C6A7776C007}.Release|ARM.ActiveCfg = Release|ARM + {00007670-DFFC-5059-A04E-7C6A7776C007}.Release|ARM.Build.0 = Release|ARM + {00007670-DFFC-5059-A04E-7C6A7776C007}.Release|ARM.Deploy.0 = Release|ARM + {00007670-DFFC-5059-A04E-7C6A7776C007}.Release|x64.ActiveCfg = Release|x64 + {00007670-DFFC-5059-A04E-7C6A7776C007}.Release|x64.Build.0 = Release|x64 + {00007670-DFFC-5059-A04E-7C6A7776C007}.Release|x64.Deploy.0 = Release|x64 + {00007670-DFFC-5059-A04E-7C6A7776C007}.Release|x86.ActiveCfg = Release|x86 + {00007670-DFFC-5059-A04E-7C6A7776C007}.Release|x86.Build.0 = Release|x86 + {00007670-DFFC-5059-A04E-7C6A7776C007}.Release|x86.Deploy.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4A8DD9EE-71F2-4C28-B3A5-1B0848B1B623} + EndGlobalSection +EndGlobal diff --git a/Samples/MixedRealityModel/cs/Package.appxmanifest b/Samples/MixedRealityModel/cs/Package.appxmanifest new file mode 100644 index 0000000000..dc66d23763 --- /dev/null +++ b/Samples/MixedRealityModel/cs/Package.appxmanifest @@ -0,0 +1,50 @@ + + + + + + + + + MixedRealityModel C# Sample + Microsoft Corporation + Assets\StoreLogo-sdk.png + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/MixedRealityModel/cs/SampleConfiguration.cs b/Samples/MixedRealityModel/cs/SampleConfiguration.cs new file mode 100644 index 0000000000..26aeb037fe --- /dev/null +++ b/Samples/MixedRealityModel/cs/SampleConfiguration.cs @@ -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. +// +//********************************************************* + +using System; +using System.Collections.Generic; +using Windows.UI.Xaml.Controls; + +namespace SDKTemplate +{ + public partial class MainPage : Page + { + public const string FEATURE_NAME = "Mixed Reality Model C# Sample"; + + List scenarios = new List + { + new Scenario() { Title="Place 3D model", ClassType=typeof(SDKTemplate.Scenario1_CreateTile)}, + }; + } + + public class Scenario + { + public string Title { get; set; } + public Type ClassType { get; set; } + } +} diff --git a/Samples/MixedRealityModel/cs/Scenario1_CreateTile.xaml b/Samples/MixedRealityModel/cs/Scenario1_CreateTile.xaml new file mode 100644 index 0000000000..957bd3c671 --- /dev/null +++ b/Samples/MixedRealityModel/cs/Scenario1_CreateTile.xaml @@ -0,0 +1,76 @@ + + + + + + + + Place a 3D model. + + + + + + - - - In Windows 10 on the PC, - the touch keyboard does not display automatically if a hardware keyboard is connected or if the PC is in Desktop mode and - "Automatically show the touch keyboard in windowed apps when there is no keyboard attached to your device" - in Settings -> Devices -> Typing is disabled. - - - - + + + + + Set InputPaneDisplayPolicy to Manual to disable the touch keyboard + until manually invoked by the user. + To add the Touch keyboard button to the taskbar, right-click on the taskbar + and select the "Show touch keyboard button" item in the context menu. + + Prevent touch keyboard from showing on focus change + + + See the note in scenario 1 for the rules regarding the touch keyboard. + + + diff --git a/Samples/TouchKeyboard/Shared/Scenario2_ShowHideEvents.xaml b/Samples/TouchKeyboard/Shared/Scenario2_ShowHideEvents.xaml new file mode 100644 index 0000000000..d46a3742c9 --- /dev/null +++ b/Samples/TouchKeyboard/Shared/Scenario2_ShowHideEvents.xaml @@ -0,0 +1,43 @@ + + + + + + + + + The Showing and Hiding events are raised when the touch keyboard is shown or hidden. + + + + Last event: + + + + + + +

+
+

+

+
+ + + \ No newline at end of file diff --git a/Samples/WebSocket/js/js/sample-configuration.js b/Samples/WebSocket/js/js/sample-configuration.js index b86fd1dddb..9a246b6bfa 100644 --- a/Samples/WebSocket/js/js/sample-configuration.js +++ b/Samples/WebSocket/js/js/sample-configuration.js @@ -19,7 +19,8 @@ var scenarios = [ { url: "/html/scenario1-utf8.html", title: "UTF-8 text messages" }, - { url: "/html/scenario2-binary.html", title: "Binary data stream" } + { url: "/html/scenario2-binary.html", title: "Binary data stream" }, + { url: "/html/scenario3-clientCertificate.html", title: "Client certificate" } ]; // Look up the name for an enumeration member. diff --git a/Samples/WebSocket/js/js/scenario3-clientCertificate.js b/Samples/WebSocket/js/js/scenario3-clientCertificate.js new file mode 100644 index 0000000000..69571dd9e5 --- /dev/null +++ b/Samples/WebSocket/js/js/scenario3-clientCertificate.js @@ -0,0 +1,223 @@ +//********************************************************* +// +// 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. +// +//********************************************************* + +(function () { + "use strict"; + + const Certificates = Windows.Security.Cryptography.Certificates; + const ChainValidationResult = Certificates.ChainValidationResult; + + // Local variables + var streamWebSocket; + var busy; + + // DOM elements + var serverAddressField; + var connectButton; + var disconnectButton; + var outputField; + + // Certificate information + const clientCertUriPath = "ms-appx:///data/clientCert.pfx"; + const clientCertPassword = "1234"; + const clientCertFriendlyName = "WebSocketSampleClientCert"; + const clientCertIssuerName = "www.contoso.com"; + + var page = WinJS.UI.Pages.define("/html/scenario3-clientCertificate.html", { + ready: function (element, options) { + serverAddressField = document.getElementById("serverAddressField"); + connectButton = document.getElementById("connectButton"); + disconnectButton = document.getElementById("disconnectButton"); + outputField = document.getElementById("outputField"); + + connectButton.addEventListener("click", onConnect, false); + disconnectButton.addEventListener("click", onDisconnect, false); + + updateVisualState(); + }, + unload: function () { + closeSocket(); + } + }); + + function updateVisualState() { + serverAddressField.disabled = busy || streamWebSocket; + connectButton.disabled = busy || streamWebSocket; + disconnectButton.disabled = busy || !streamWebSocket; + } + + function setBusy(value) { + busy = value; + updateVisualState(); + } + + function onConnect() { + setBusy(true); + connectWebSocketAsync().done(function () { + setBusy(false); + }); + } + + function findCertificateFromStoreAsync() { + // Find the client certificate for authentication. If not found, it means it has not been installed. + var query = new Certificates.CertificateQuery(); + query.issuerName = clientCertIssuerName; + query.friendlyName = clientCertFriendlyName; + + return Certificates.CertificateStores.findAllAsync(query).then(function (certs) { + // This sample installs only one certificate, so if we find one, it must be ours. + return certs.length && certs[0]; + }); + } + + function installClientCertificateAsync() { + // Load the certificate from the clientCert.pfx file packaged with this sample. + // This certificate has been signed with a trusted root certificate installed on the server. + // The installation is done by running the setupServer.ps1 file, which should have been done + // before running the app. + // WARNING: Including a pfx file in the app package violates the Windows Store + // certification requirements. We are shipping the pfx file with the package for demonstrating + // the usage of client certificates. Apps that will be published through Windows Store + // need to use other approaches to obtain a client certificate. + return Windows.Storage.StorageFile.getFileFromApplicationUriAsync(new Windows.Foundation.Uri(clientCertUriPath) + ).then(function (clientCertFile) { + return Windows.Storage.FileIO.readBufferAsync(clientCertFile); + }).then(function (buffer) { + appendOutputLine("Reading certificate succeeded."); + var clientCertData = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer); + + // Install the certificate to the app's certificate store. + // The app certificate store is removed when the app is uninstalled. + // + // To install a certificate to the CurrentUser\MY store, which is not app specific, + // you need to use CertificateEnrollmentManager.UserCertificateEnrollmentManager.importPfxDataAsync(). + // In order to call that method, an app must have the "sharedUserCertificates" capability. + // There are two ways to add this capability: + // + // 1. You can double click on the Package.appxmanifest file from the + // solution explorer, select the "Capabilities" tab in the opened page, and then check the + // "Shared User Certificates" box from the capabilities list. + // 2. You can right click on the Package.appxmanifest file from the + // solution explorer, select "View Code", and add "sharedUserCertificates" under the + // element directly. + // Package.appxmanifest in this sample shows this capability commented out. + // + // In this case, the certificate will remain even when the app is uninstalled. + return Certificates.CertificateEnrollmentManager.importPfxDataAsync( + clientCertData, + clientCertPassword, + Certificates.ExportOption.exportable, + Certificates.KeyProtectionLevel.noConsent, + Certificates.InstallOptions.deleteExpired, + clientCertFriendlyName); + }).then(function () { + appendOutputLine("Installing certificate succeeded."); + + // Return the certificate we just installed. + return findCertificateFromStoreAsync(); + }, function error(e) { + // This can happen if the certificate is corrupted or has already expired. + appendOutputLine("Installing certificate failed: " + e.message); + }); + } + + function getClientCertificateAsync() { + // The client certificate could be installed already (if this scenario has been run before), + // try finding it from the store. + return findCertificateFromStoreAsync().then(function (cert) { + // If the client certificate is not already installed, install and return it. + return cert || installClientCertificateAsync(); + }); + } + + function connectWebSocketAsync() { + // Validating the URI is required since it was received from an untrusted source (user + // input). The URI is validated by calling validateAndCreateUri() that will return null + // for strings that are not valid WebSocket URIs. + // Note that when enabling the text box users may provide URIs to machines on the local network + // or internet. In these cases the app requires the "Home or Work Networking" or + // "Internet (Client)" capability respectively. + var server = SdkSample.validateAndCreateUri(serverAddressField.value); + if (!server) { + return; + } + + // Certificate validation is meaningful only for secure connections. + if (server.schemeName !== "wss") { + appendOutputLine("Note: Certificate validation is performed only for the wss: scheme."); + } + + streamWebSocket = new Windows.Networking.Sockets.StreamWebSocket(); + streamWebSocket.addEventListener("closed", onClosed); + + return getClientCertificateAsync().then(function (cert) { + // It is okay to set the ClientCertificate property even if GetClientCertificateAsync returns null. + streamWebSocket.control.clientCertificate = cert; + + // When connecting to wss:// endpoint, the OS by default performs validation of + // the server certificate based on well-known trusted CAs. For testing purposes, we are ignoring + // SSL errors. + // WARNING: Only test applications should ignore SSL errors. + // In real applications, ignoring server certificate errors can lead to Man-In-The-Middle + // attacks. (Although the connection is secure, the server is not authenticated.) + // Note that not all certificate validation errors can be ignored. + // In this case, we are ignoring these errors since the certificate assigned to the localhost + // URI is self-signed and has subject name = fabrikam.com + streamWebSocket.control.ignorableServerCertificateErrors.push( + ChainValidationResult.untrusted, + ChainValidationResult.invalidName); + + appendOutputLine("Connecting to: " + server.absoluteUri); + + return streamWebSocket.connectAsync(server); + }).then(function () { + WinJS.log && WinJS.log("Connected", "sample", "status"); + appendOutputLine("Connected to WebSocket Server."); + }, function error(e) { + streamWebSocket.close(); + streamWebSocket = null; + + appendOutputLine(SdkSample.buildWebSocketError(e)); + appendOutputLine(e.message); + }); + } + + function onDisconnect() { + setBusy(true); + WinJS.log && WinJS.log("Closing", "sample", "status"); + closeSocket(); + setBusy(false); + } + + function onClosed(e) { + appendOutputLine("Closed; Code: " + e.code + " Reason: " + e.reason); + if (streamWebSocket === e.target) { + closeSocket(); + updateVisualState(); + } + } + + function closeSocket() { + if (streamWebSocket) { + try { + streamWebSocket.close(1000, "Closed due to user request."); + } catch (error) { + WinJS.log && WinJS.log(SdkSample.buildWebSocketError(error), "sample", "error"); + appendOutputLine(error.message); + } + streamWebSocket = null; + } + } + + function appendOutputLine(text) { + outputField.innerText += "\r\n" + text; + } +})(); diff --git a/Samples/WebSocket/server/RootCert.cer b/Samples/WebSocket/server/RootCert.cer new file mode 100644 index 0000000000000000000000000000000000000000..bcde2e4fe3b81919b7a2445b9a57fb541fc76685 GIT binary patch literal 824 zcmXqLVm2{oVvqx@wXhpzZKJ`0=*|t4dP0q-LLt>shX? zvRyizVNJ8tq}l8y6Xwra_y5ZO^awqlNOsnHJAy@IrwBeXo#5?tP1mnmTySy9+>&nz z4IXoXlXq=DP_|X+-o=%Y(Rvcxn!5{LKMkSrH20tZ=?Q-uciKCR@==NrF~XP z+qiw>rbL$s6^Guf72l^aF*7nSE-o`DG2jD+zAQf@<9`+w zV3f5PNP+l*EW!qYO&BpE43bl2;W6N1CI5>k zj|2NtIsVA3)9;UeTbilzu07x--(0_zE6e?#itoMk|83o&j0;Ua+B96{kN#~-bv-;s zNWEiKruK6EN4(x?AA=Z@eD1!uv3jOO;b&IUSB(<)uBZM=T$pIId)17pZ5wB-eR5;| ziDy}{d zgAc8qGP!y|W13=cTX!tOL5t-#dY$I)BPx+3!ravbH%pok#!x)=Wu$ literal 0 HcmV?d00001 diff --git a/Samples/WebSocket/server/removeserver.ps1 b/Samples/WebSocket/server/removeserver.ps1 index 8a8dd338a3..ff3380a722 100644 --- a/Samples/WebSocket/server/removeserver.ps1 +++ b/Samples/WebSocket/server/removeserver.ps1 @@ -44,8 +44,11 @@ Remove-NetFirewallRule -DisplayName $wsfirewallRuleName > $null "Removing firewall rule `'$wsssFirewallRuleName`'." Remove-NetFirewallRule -DisplayName $wssFirewallRuleName > $null -"Removing certificate with thumbprint " + $settings.certificateThumbprint -gci cert:\ -Recurse | where {$_.Thumbprint -eq $settings.certificateThumbprint } | Remove-Item +"Removing server certificate with thumbprint " + $settings.serverCertificateThumbprint +gci cert:\LocalMachine -Recurse | where {$_.Thumbprint -eq $settings.serverCertificateThumbprint} | Remove-Item +"Removing client certificate root with thumbprint " + $settings.clientRootCertThumbprint +gci cert:\LocalMachine -Recurse | where {$_.Thumbprint -eq $settings.clientRootCertThumbprint} | Remove-Item +gci cert:\CurrentUser -Recurse | where {$_.Thumbprint -eq $settings.clientRootCertThumbprint} | Remove-Item if ($settings.webBindingAdded) { diff --git a/Samples/WebSocket/server/setupserver.ps1 b/Samples/WebSocket/server/setupserver.ps1 index 80e397e512..5b0a8a67ba 100644 --- a/Samples/WebSocket/server/setupserver.ps1 +++ b/Samples/WebSocket/server/setupserver.ps1 @@ -13,10 +13,12 @@ $scriptPath = $(Split-Path $MyInvocation.MyCommand.Path) $iisAppName = "WebSocketSample" $iisAppPath = "$env:systemdrive\inetpub\wwwroot\WebSocketSample" $websitePath = "$scriptPath\website" +$webpageFileName = "echowebsocket.ashx" +$clientCertWebpageFileName = "EchoWebSocketWithClientAuthentication.ashx" $wsfirewallRuleName = "WebSocketSample - HTTP 80" $wssFirewallRuleName = "WebSocketSample - HTTPS 443" $requiredFeatures = "IIS-WebServer", "IIS-WebServerRole", "NetFx4Extended-ASPNET45", "IIS-ApplicationDevelopment", "IIS-ASPNET45", "IIS-ISAPIExtensions", "IIS-ISAPIFilter", "IIS-NetFxExtensibility45", "IIS-WebSockets" -$settings = @{"featuresToEnable"=@(); "certificateThumbprint"=""; "webBindingAdded"=$false; "oldDefaultCert"=""} +$settings = @{"featuresToEnable"=@(); "serverCertificateThumbprint"=""; "clientRootCertThumbprint" = ""; "webBindingAdded"=$false; "oldDefaultCert"=""} $settingsFile = "$scriptPath\WebSocketSampleScriptSettings" # Check if running as Administrator. @@ -52,7 +54,8 @@ if ($featuresToEnable.Count -gt 0) if (-not (Test-Path $iisAppPath)) { mkdir $iisAppPath > $null - Copy-Item $websitePath\* $iisAppPath -r + Copy-Item $websitePath\$webpageFileName $iisAppPath + Copy-Item $websitePath\$webpageFileName $iisAppPath\$clientCertWebpageFileName } # Add web application. @@ -68,6 +71,7 @@ if ($(Get-WebApplication $iisAppName) -eq $null) "type"="System.Diagnostics.TextWriterTraceListener" ` } Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/Default Web Site/$iisAppName" -filter "system.diagnostics/trace" -name "autoflush" -value "true" + Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" -location "/Default Web Site/$iisAppName/EchoWebSocketWithClientAuthentication.ashx" -filter "system.webServer/security/access" -name "sslFlags" -value "Ssl,SslNegotiateCert,SslRequireCert" # Allow IIS_IUSRS to write to this directory. This is necessary if a log file is created. $acl = Get-Acl $iisAppPath @@ -83,7 +87,10 @@ else # Creating self-signed certificate and assigning it to the default binding "Creating self-signed certificate." -$cert = New-SelfSignedCertificate -DnsName www.fabrikam.com -CertStoreLocation cert:\LocalMachine\My +$serverCert = New-SelfSignedCertificate -DnsName "www.fabrikam.com" -CertStoreLocation "cert:\LocalMachine\My" + +"Importing a self-signed root cert to trusted root, which has been used to sign the client cert" +$clientRootCert = Import-Certificate -FilePath ".\Rootcert.cer" -CertStoreLocation "cert:\LocalMachine\Root" $binding = Get-WebBinding "Default Web Site" -Protocol "https" -Port 443 -IPAddress "*" if ($binding -eq $null) @@ -103,7 +110,7 @@ if(Test-Path IIS:\SslBindings\0.0.0.0!443) "Assigning certificate to the default binding" -$cert | New-Item IIS:\SslBindings\0.0.0.0!443 -Force | out-null +$serverCert | New-Item IIS:\SslBindings\0.0.0.0!443 -Force | out-null # Add firewall rules. "Adding firewall rule `'$wsfirewallRuleName`'." @@ -112,5 +119,6 @@ New-NetFirewallRule -DisplayName $wsfirewallRuleName -Direction Inbound -Protoco New-NetFirewallRule -DisplayName $wssfirewallRuleName -Direction Inbound -Protocol TCP -LocalPort 443 -Action Allow > $null "Saving settings for uninstall" -$settings.certificateThumbprint = $cert.Thumbprint -$settings | Export-Clixml -Path $settingsFile \ No newline at end of file +$settings.serverCertificateThumbprint = $serverCert.Thumbprint +$settings.clientRootCertThumbprint = $clientRootCert.Thumbprint +$settings | Export-Clixml -Path $settingsFile diff --git a/Samples/WebSocket/server/website/echowebsocket.ashx b/Samples/WebSocket/server/website/echowebsocket.ashx index 45b0e061bb..9b7fea7fd0 100644 --- a/Samples/WebSocket/server/website/echowebsocket.ashx +++ b/Samples/WebSocket/server/website/echowebsocket.ashx @@ -1,17 +1,26 @@ -<%@ WebHandler Language="C#" Class="EchoWebSocket" %> + + +<%@ WebHandler Language="C#" Class="EchoWebSocket" %> // This define is needed to enable System.Diagnostics.Trace methods. For more information go to: // http://msdn.microsoft.com/en-us/library/system.diagnostics.trace(v=vs.110).aspx #define TRACE using System; -using System.Web; using System.Net.WebSockets; -using System.Web.WebSockets; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Web; +using System.Web.WebSockets; public class EchoWebSocket : IHttpHandler { private const int MaxBufferSize = 64 * 1024; diff --git a/Samples/WebSocket/shared/Scenario3_ClientAuthentication.xaml b/Samples/WebSocket/shared/Scenario3_ClientAuthentication.xaml new file mode 100644 index 0000000000..f36c52ccc3 --- /dev/null +++ b/Samples/WebSocket/shared/Scenario3_ClientAuthentication.xaml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + This scenario shows how to use a StreamWebSocket with a client certificate to connect to a server. + The sample server (localhost) requires requires a secure WebSocket (wss://) connection with a client certificate. + The StreamWebSocket client certificate property behaves the same as the MessageWebSocket client certificate property. + + + + +