| // Copyright 2017 The Cobalt Authors. All Rights Reserved. | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //     http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 |  | 
 | #include "starboard/shared/uwp/application_uwp.h" | 
 |  | 
 | #include <D3D11.h> | 
 | #include <D3D11_1.h> | 
 | #include <WinSock2.h> | 
 | #include <collection.h> | 
 | #include <mfapi.h> | 
 | #include <ppltasks.h> | 
 | #include <windows.graphics.display.core.h> | 
 | #include <windows.h> | 
 | #include <windows.system.display.h> | 
 |  | 
 | #include <cstdlib> | 
 | #include <memory> | 
 | #include <string> | 
 | #include <vector> | 
 |  | 
 | #include "starboard/common/device_type.h" | 
 | #include "starboard/common/file.h" | 
 | #include "starboard/common/log.h" | 
 | #include "starboard/common/mutex.h" | 
 | #include "starboard/common/queue.h" | 
 | #include "starboard/common/semaphore.h" | 
 | #include "starboard/common/string.h" | 
 | #include "starboard/common/system_property.h" | 
 | #include "starboard/common/thread.h" | 
 | #include "starboard/configuration_constants.h" | 
 | #include "starboard/event.h" | 
 | #include "starboard/input.h" | 
 | #include "starboard/shared/starboard/application.h" | 
 | #include "starboard/shared/starboard/audio_sink/audio_sink_internal.h" | 
 | #include "starboard/shared/starboard/media/key_system_supportability_cache.h" | 
 | #include "starboard/shared/starboard/media/mime_supportability_cache.h" | 
 | #include "starboard/shared/starboard/net_args.h" | 
 | #include "starboard/shared/starboard/net_log.h" | 
 | #include "starboard/shared/uwp/analog_thumbstick_input_thread.h" | 
 | #include "starboard/shared/uwp/app_accessors.h" | 
 | #include "starboard/shared/uwp/async_utils.h" | 
 | #include "starboard/shared/uwp/extended_resources_manager.h" | 
 | #include "starboard/shared/uwp/log_file_impl.h" | 
 | #include "starboard/shared/uwp/watchdog_log.h" | 
 | #include "starboard/shared/uwp/window_internal.h" | 
 | #include "starboard/shared/win32/thread_private.h" | 
 | #include "starboard/shared/win32/video_decoder.h" | 
 | #include "starboard/shared/win32/wchar_utils.h" | 
 | #include "starboard/system.h" | 
 |  | 
 | namespace starboard { | 
 |  | 
 | using Microsoft::WRL::ComPtr; | 
 | using shared::starboard::Application; | 
 | using shared::starboard::CommandLine; | 
 | using shared::starboard::kNetArgsCommandSwitchWait; | 
 | using shared::starboard::kNetLogCommandSwitchWait; | 
 | using shared::starboard::NetArgsWaitForPayload; | 
 | using shared::starboard::NetLogFlushThenClose; | 
 | using shared::starboard::NetLogWaitForClientConnected; | 
 | using shared::uwp::ApplicationUwp; | 
 | using shared::uwp::RunInMainThreadAsync; | 
 | using shared::uwp::WaitForResult; | 
 | using shared::win32::platformStringToString; | 
 | using shared::win32::stringToPlatformString; | 
 | using shared::win32::wchar_tToUTF8; | 
 | using ::starboard::shared::starboard::media::KeySystemSupportabilityCache; | 
 | using ::starboard::shared::starboard::media::MimeSupportabilityCache; | 
 | using Windows::ApplicationModel::SuspendingDeferral; | 
 | using Windows::ApplicationModel::SuspendingEventArgs; | 
 | using Windows::ApplicationModel::Activation::ActivationKind; | 
 | using Windows::ApplicationModel::Activation::DialReceiverActivatedEventArgs; | 
 | using Windows::ApplicationModel::Activation::IActivatedEventArgs; | 
 | using Windows::ApplicationModel::Activation::IProtocolActivatedEventArgs; | 
 | using Windows::ApplicationModel::Core::CoreApplication; | 
 | using Windows::ApplicationModel::Core::CoreApplicationView; | 
 | using Windows::ApplicationModel::Core::IFrameworkView; | 
 | using Windows::ApplicationModel::Core::IFrameworkViewSource; | 
 | using Windows::ApplicationModel::ExtendedExecution::ExtendedExecutionReason; | 
 | using Windows::ApplicationModel::ExtendedExecution::ExtendedExecutionResult; | 
 | using Windows::ApplicationModel::ExtendedExecution:: | 
 |     ExtendedExecutionRevokedEventArgs; | 
 | using Windows::ApplicationModel::ExtendedExecution::ExtendedExecutionSession; | 
 | using Windows::Devices::Enumeration::DeviceInformation; | 
 | using Windows::Devices::Enumeration::DeviceInformationUpdate; | 
 | using Windows::Devices::Enumeration::DeviceWatcher; | 
 | using Windows::Devices::Enumeration::DeviceWatcherStatus; | 
 | using Windows::Foundation::EventHandler; | 
 | using Windows::Foundation::IAsyncOperation; | 
 | using Windows::Foundation::TimeSpan; | 
 | using Windows::Foundation::TypedEventHandler; | 
 | using Windows::Foundation::Uri; | 
 | using Windows::Foundation::Collections::IVectorView; | 
 | using Windows::Foundation::Metadata::ApiInformation; | 
 | using Windows::Globalization::Calendar; | 
 | using Windows::Graphics::Display::AdvancedColorInfo; | 
 | using Windows::Graphics::Display::AdvancedColorKind; | 
 | using Windows::Graphics::Display::DisplayInformation; | 
 | using Windows::Graphics::Display::HdrMetadataFormat; | 
 | using Windows::Graphics::Display::Core::HdmiDisplayColorSpace; | 
 | using Windows::Graphics::Display::Core::HdmiDisplayHdr2086Metadata; | 
 | using Windows::Graphics::Display::Core::HdmiDisplayHdrOption; | 
 | using Windows::Graphics::Display::Core::HdmiDisplayInformation; | 
 | using Windows::Graphics::Display::Core::HdmiDisplayMode; | 
 | using Windows::Media::Protection::HdcpProtection; | 
 | using Windows::Media::Protection::HdcpSession; | 
 | using Windows::Media::Protection::HdcpSetProtectionResult; | 
 | using Windows::Security::Authentication::Web::Core::WebTokenRequestResult; | 
 | using Windows::Security::Authentication::Web::Core::WebTokenRequestStatus; | 
 | using Windows::Security::Credentials::WebAccountProvider; | 
 | using Windows::Storage::FileAttributes; | 
 | using Windows::Storage::KnownFolders; | 
 | using Windows::Storage::StorageFile; | 
 | using Windows::Storage::StorageFolder; | 
 | using Windows::System::UserAuthenticationStatus; | 
 | using Windows::System::Threading::ThreadPoolTimer; | 
 | using Windows::System::Threading::TimerElapsedHandler; | 
 | using Windows::UI::Core::CoreDispatcherPriority; | 
 | using Windows::UI::Core::CoreProcessEventsOption; | 
 | using Windows::UI::Core::CoreWindow; | 
 | using Windows::UI::Core::DispatchedHandler; | 
 | using Windows::UI::Core::KeyEventArgs; | 
 | using Windows::UI::ViewManagement::ApplicationView; | 
 | using Windows::UI::ViewManagement::ApplicationViewScaling; | 
 |  | 
 | namespace { | 
 |  | 
 | const Platform::String ^ kGenericPnpMonitorAqs = ref new Platform::String( | 
 |     L"System.Devices.InterfaceClassGuid:=\"{e6f07b5f-ee97-4a90-b076-" | 
 |     L"33f57bf4eaa7}\" AND " | 
 |     L"System.Devices.InterfaceEnabled:=System.StructuredQueryType.Boolean#" | 
 |     L"True"); | 
 |  | 
 | const uint32_t kYuv420BitsPerPixelForHdr10Mode = 24; | 
 |  | 
 | // The number of seconds to wait after requesting application stop. This is used | 
 | // to exit the app when a suspend has been requested. This is a temporary | 
 | // behavior that should be removed when not needed anymore. | 
 | const SbTime kAppExitWaitTime = kSbTimeSecond * 30; | 
 |  | 
 | // Per Microsoft, HdcpProtection::On means HDCP 1.x required. | 
 | const HdcpProtection kHDCPProtectionMode = HdcpProtection::On; | 
 |  | 
 | const int kWinSockVersionMajor = 2; | 
 | const int kWinSockVersionMinor = 2; | 
 |  | 
 | const char kDialParamPrefix[] = "cobalt-dial:?"; | 
 | const char kLogPathSwitch[] = "xb1_log_file"; | 
 | // A special log that the app will periodically write to. This allows | 
 | // tests to determine if the app is still alive. | 
 | const char kWatchDogLog[] = "xb1_watchdog_log"; | 
 | const char kStarboardArgumentsPath[] = "arguments\\starboard_arguments.txt"; | 
 | const int64_t kMaxArgumentFileSizeBytes = 4 * 1024 * 1024; | 
 |  | 
 | int main_return_value = 0; | 
 |  | 
 | // IDisplayRequest is both "non-agile" and apparently | 
 | // incompatible with Platform::Agile (it doesn't fully implement | 
 | // a thread marshaller). We must neither use ComPtr or Platform::Agile | 
 | // here. We manually create, access release on the main app thread only. | 
 | ABI::Windows::System::Display::IDisplayRequest* display_request = nullptr; | 
 |  | 
 | // If an argv[0] is required, fill it in with the result of | 
 | // GetModuleFileName() | 
 | std::string GetArgvZero() { | 
 |   const size_t kMaxModuleNameSize = kSbFileMaxName; | 
 |   std::vector<wchar_t> buffer(kMaxModuleNameSize); | 
 |   DWORD result = GetModuleFileName(NULL, buffer.data(), buffer.size()); | 
 |   std::string arg; | 
 |   if (result == 0) { | 
 |     arg = "unknown"; | 
 |   } else { | 
 |     arg = wchar_tToUTF8(buffer.data(), result).c_str(); | 
 |   } | 
 |   return arg; | 
 | } | 
 |  | 
 | int MakeDeviceId() { | 
 |   // TODO: Devices MIGHT have colliding hashcodes. Some other unique int | 
 |   // ID generation tool would be better. | 
 |   using Windows::Security::ExchangeActiveSyncProvisioning:: | 
 |       EasClientDeviceInformation; | 
 |   auto device_information = ref new EasClientDeviceInformation(); | 
 |   Platform::String ^ device_id_string = device_information->Id.ToString(); | 
 |   return device_id_string->GetHashCode(); | 
 | } | 
 |  | 
 | #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) | 
 |  | 
 | void SplitArgumentsIntoVector(std::string* args, | 
 |                               std::vector<std::string>* result) { | 
 |   SB_DCHECK(args); | 
 |   SB_DCHECK(result); | 
 |   while (!args->empty()) { | 
 |     size_t next = args->find(';'); | 
 |     result->push_back(args->substr(0, next)); | 
 |     if (next == std::string::npos) { | 
 |       return; | 
 |     } | 
 |     *args = args->substr(next + 1); | 
 |   } | 
 | } | 
 |  | 
 | // Parses a starboard: URI scheme by splitting args at ';' boundaries. | 
 | std::vector<std::string> ParseStarboardUri(const std::string& uri) { | 
 |   std::vector<std::string> result; | 
 |   result.push_back(GetArgvZero()); | 
 |  | 
 |   size_t index = uri.find(':'); | 
 |   if (index == std::string::npos) { | 
 |     return result; | 
 |   } | 
 |  | 
 |   std::string args = uri.substr(index + 1); | 
 |   SplitArgumentsIntoVector(&args, &result); | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | void AddArgumentsFromFile(const char* path, std::vector<std::string>* args) { | 
 |   ScopedFile file(path, kSbFileOpenOnly | kSbFileRead); | 
 |   if (!file.IsValid()) { | 
 |     SB_LOG(INFO) << path << " is not valid for arguments."; | 
 |     return; | 
 |   } | 
 |  | 
 |   int64_t file_size = file.GetSize(); | 
 |   if (file_size > kMaxArgumentFileSizeBytes) { | 
 |     SB_DLOG(ERROR) << "The arguments file is too big."; | 
 |     return; | 
 |   } | 
 |  | 
 |   if (file_size <= 0) { | 
 |     SB_DLOG(INFO) << "Arguments file is empty."; | 
 |     return; | 
 |   } | 
 |  | 
 |   std::string argument_string(file_size, '\0'); | 
 |   int return_value = file.ReadAll(&argument_string[0], file_size); | 
 |   if (return_value < 0) { | 
 |     SB_DLOG(ERROR) << "Error while reading arguments from file."; | 
 |     return; | 
 |   } | 
 |   argument_string.resize(return_value); | 
 |  | 
 |   SplitArgumentsIntoVector(&argument_string, args); | 
 | } | 
 |  | 
 | void TryAddCommandArgsFromStarboardFile(std::vector<std::string>* args) { | 
 |   std::vector<char> content_directory(kSbFileMaxName); | 
 |   content_directory[0] = '\0'; | 
 |  | 
 |   if (!SbSystemGetPath(kSbSystemPathContentDirectory, content_directory.data(), | 
 |                        content_directory.size())) { | 
 |     return; | 
 |   } | 
 |  | 
 |   std::string arguments_file_path(static_cast<char*>(content_directory.data())); | 
 |   arguments_file_path += kSbFileSepString; | 
 |   arguments_file_path += kStarboardArgumentsPath; | 
 |  | 
 |   AddArgumentsFromFile(arguments_file_path.c_str(), args); | 
 | } | 
 |  | 
 | void AddCommandArgsFromNetArgs(SbTime timeout, std::vector<std::string>* args) { | 
 |   // Detect if NetArgs is enabled for this run. If so then receive and | 
 |   // then merge the arguments into this run. | 
 |   SB_LOG(INFO) << "Waiting for net args..."; | 
 |   std::vector<std::string> net_args = NetArgsWaitForPayload(timeout); | 
 |   if (!net_args.empty()) { | 
 |     std::stringstream ss; | 
 |     ss << "Found Net Args:\n"; | 
 |     for (const std::string& s : net_args) { | 
 |       ss << "  " << s << "\n"; | 
 |     } | 
 |     SB_LOG(INFO) << ss.str(); | 
 |   } | 
 |   // Merge command arguments. | 
 |   args->insert(args->end(), net_args.begin(), net_args.end()); | 
 | } | 
 |  | 
 | #endif  // defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) | 
 |  | 
 | std::unique_ptr<Application::Event> MakeDeepLinkEvent( | 
 |     const std::string& uri_string) { | 
 |   SB_LOG(INFO) << "Navigate to: [" << uri_string << "]"; | 
 |   const size_t kMaxDeepLinkSize = 128 * 1024; | 
 |   const std::size_t uri_size = uri_string.size(); | 
 |   if (uri_size > kMaxDeepLinkSize) { | 
 |     SB_NOTREACHED() << "App launch data too big: " << uri_size; | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   const int kBufferSize = static_cast<int>(uri_string.size()) + 1; | 
 |   char* deep_link = new char[kBufferSize]; | 
 |   SB_DCHECK(deep_link); | 
 |   starboard::strlcpy(deep_link, uri_string.c_str(), kBufferSize); | 
 |  | 
 |   return std::unique_ptr<Application::Event>( | 
 |       new Application::Event(kSbEventTypeLink, deep_link, | 
 |                              Application::DeleteArrayDestructor<const char*>)); | 
 | } | 
 |  | 
 | // Returns if |full_string| ends with |substring|. | 
 | bool ends_with(const std::string& full_string, const std::string& substring) { | 
 |   if (substring.length() > full_string.length()) { | 
 |     return false; | 
 |   } | 
 |   return std::equal(substring.rbegin(), substring.rend(), full_string.rbegin()); | 
 | } | 
 |  | 
 | std::string GetBinaryName() { | 
 |   std::string full_binary_path = GetArgvZero(); | 
 |   std::string::size_type index = full_binary_path.rfind(kSbFileSepChar); | 
 |   if (index == std::string::npos) { | 
 |     return full_binary_path; | 
 |   } | 
 |  | 
 |   return full_binary_path.substr(index + 1); | 
 | } | 
 |  | 
 | void OnDeviceAdded(DeviceWatcher ^, DeviceInformation ^) { | 
 |   SB_LOG(INFO) << "DisplayStatusWatcher::OnDeviceAdded"; | 
 |   // We need delay to give time for the display initializing after connect. | 
 |   SbThreadSleep(15 * kSbTimeMillisecond); | 
 |  | 
 |   MimeSupportabilityCache::GetInstance()->ClearCachedMimeSupportabilities(); | 
 |  | 
 |   ApplicationUwp::Get()->Inject( | 
 |       new ApplicationUwp::Event(kSbEventTypeUnfreeze, NULL, NULL)); | 
 |   ApplicationUwp::Get()->Inject( | 
 |       new ApplicationUwp::Event(kSbEventTypeReveal, NULL, NULL)); | 
 |   ApplicationUwp::Get()->Inject( | 
 |       new ApplicationUwp::Event(kSbEventTypeFocus, NULL, NULL)); | 
 | } | 
 |  | 
 | void OnDeviceRemoved(DeviceWatcher ^, DeviceInformationUpdate ^) { | 
 |   // Without signing on OnDeviceRemoved, callback OnDeviceAdded doesn't work. | 
 |   SB_LOG(INFO) << "DisplayStatusWatcher::OnDeviceRemoved"; | 
 |  | 
 |   MimeSupportabilityCache::GetInstance()->ClearCachedMimeSupportabilities(); | 
 |  | 
 |   ApplicationUwp::Get()->Inject( | 
 |       new ApplicationUwp::Event(kSbEventTypeBlur, NULL, NULL)); | 
 |   ApplicationUwp::Get()->Inject( | 
 |       new ApplicationUwp::Event(kSbEventTypeConceal, NULL, NULL)); | 
 |   ApplicationUwp::Get()->Inject( | 
 |       new ApplicationUwp::Event(kSbEventTypeFreeze, NULL, NULL)); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | namespace shared { | 
 | namespace win32 { | 
 | // Called into drm_system_playready.cc | 
 | extern void DrmSystemOnUwpResume(); | 
 | }  // namespace win32 | 
 | }  // namespace shared | 
 |  | 
 | ref class App sealed : public IFrameworkView { | 
 |  public: | 
 |   App(SbTimeMonotonic start_time) | 
 |       : application_start_time_{start_time}, | 
 |         previously_activated_(false), | 
 | #if SB_API_VERSION >= 15 | 
 |         application_(SbEventHandle), | 
 | #endif  // SB_API_VERSION >= 15 | 
 |         is_online_(true) { | 
 |   } | 
 |  | 
 |   // IFrameworkView methods. | 
 |   virtual void Initialize(CoreApplicationView ^ application_view) { | 
 |     // The following incantation creates a DisplayRequest and obtains | 
 |     // its underlying COM interface. | 
 |     ComPtr<IInspectable> inspectable = reinterpret_cast<IInspectable*>( | 
 |         ref new Windows::System::Display::DisplayRequest()); | 
 |     ComPtr<ABI::Windows::System::Display::IDisplayRequest> dr; | 
 |     inspectable.As(&dr); | 
 |     display_request = dr.Detach(); | 
 |  | 
 |     SbAudioSinkPrivate::Initialize(); | 
 |     Windows::Networking::Connectivity::NetworkInformation:: | 
 |         NetworkStatusChanged += ref new Windows::Networking::Connectivity:: | 
 |             NetworkStatusChangedEventHandler(this, | 
 |                                              &App::OnNetworkStatusChanged); | 
 |     CoreApplication::Suspending += | 
 |         ref new EventHandler<SuspendingEventArgs ^>(this, &App::OnSuspending); | 
 |     CoreApplication::Resuming += | 
 |         ref new EventHandler<Object ^>(this, &App::OnResuming); | 
 |     application_view->Activated += | 
 |         ref new TypedEventHandler<CoreApplicationView ^, IActivatedEventArgs ^>( | 
 |             this, &App::OnActivated); | 
 |  | 
 |     MimeSupportabilityCache::GetInstance()->SetCacheEnabled(true); | 
 |     KeySystemSupportabilityCache::GetInstance()->SetCacheEnabled(true); | 
 |   } | 
 |  | 
 |   virtual void SetWindow(CoreWindow ^ window) { | 
 |     ApplicationUwp::Get()->SetCoreWindow(window); | 
 |     window->KeyUp += ref new TypedEventHandler<CoreWindow ^, KeyEventArgs ^>( | 
 |         this, &App::OnKeyUp); | 
 |     window->KeyDown += ref new TypedEventHandler<CoreWindow ^, KeyEventArgs ^>( | 
 |         this, &App::OnKeyDown); | 
 |   } | 
 |  | 
 |   virtual void Load(Platform::String ^ entry_point) { | 
 |     entry_point_ = wchar_tToUTF8(entry_point->Data()); | 
 |   } | 
 |  | 
 |   virtual void Run() { | 
 | #if SB_API_VERSION >= 15 | 
 |     main_return_value = | 
 |         SbRunStarboardMain(static_cast<int>(argv_.size()), | 
 |                            const_cast<char**>(argv_.data()), SbEventHandle); | 
 | #else | 
 |     main_return_value = application_.Run(static_cast<int>(argv_.size()), | 
 |                                          const_cast<char**>(argv_.data())); | 
 | #endif  // SB_API_VERSION >= 15 | 
 |   } | 
 |   virtual void Uninitialize() { | 
 |     SbAudioSinkPrivate::TearDown(); | 
 |     display_request->Release(); | 
 |     display_request = nullptr; | 
 |   } | 
 |  | 
 |   void CompleteSuspendDeferral() { | 
 |     if (suspend_deferral_ != nullptr) { | 
 |       // Completing the deferral results in the app being suspended by the OS. | 
 |       SB_LOG(INFO) << "App is ready to be suspended."; | 
 |       suspend_deferral_->Complete(); | 
 |       suspend_deferral_ = nullptr; | 
 |     } | 
 |   } | 
 |  | 
 |   void ExtendedExecutionSessionRevoked(Object ^ sender, | 
 |                                        ExtendedExecutionRevokedEventArgs ^ | 
 |                                            args) { | 
 |     CompleteSuspendDeferral(); | 
 |   } | 
 |  | 
 |   void ForceQuit() { | 
 |     SB_LOG(ERROR) << "Application is not safe to suspend, forcing exit."; | 
 |     ApplicationUwp::Get()->DispatchAndDelete( | 
 |         new ApplicationUwp::Event(kSbEventTypeStop, NULL, NULL)); | 
 |     auto extended_resources_manager = | 
 |         shared::uwp::ExtendedResourcesManager::GetInstance(); | 
 |     if (extended_resources_manager) { | 
 |       extended_resources_manager->Quit(); | 
 |     } | 
 |   } | 
 |  | 
 |   void OnNetworkStatusChanged(Object ^ sender) { | 
 |     auto connection_profile = Windows::Networking::Connectivity:: | 
 |         NetworkInformation::GetInternetConnectionProfile(); | 
 |     bool is_online = | 
 |         connection_profile && | 
 |         connection_profile->GetNetworkConnectivityLevel() != | 
 |             Windows::Networking::Connectivity::NetworkConnectivityLevel::None; | 
 |  | 
 |     // Only inject event if the online status changed. | 
 |     if (is_online != is_online_) { | 
 |       auto* application = starboard::shared::starboard::Application::Get(); | 
 |  | 
 |       // Verify we have an application and dispatcher before injecting events. | 
 |       if (application == nullptr) { | 
 |         SB_LOG(ERROR) << "OnNetworkStatusChanged has no application."; | 
 |         SB_DCHECK(false); | 
 |         return; | 
 |       } | 
 |       if (starboard::shared::uwp::GetDispatcher().Get() == nullptr) { | 
 |         SB_LOG(ERROR) << "OnNetworkStatusChanged has no dispatcher."; | 
 |         SB_DCHECK(false); | 
 |         return; | 
 |       } | 
 |  | 
 |       if (is_online) { | 
 |         application->InjectOsNetworkConnectedEvent(); | 
 |       } else { | 
 |         application->InjectOsNetworkDisconnectedEvent(); | 
 |       } | 
 |       is_online_ = is_online; | 
 |     } | 
 |   } | 
 |  | 
 |   void OnSuspending(Platform::Object ^ sender, SuspendingEventArgs ^ args) { | 
 |     // Request deferral of the suspending operation. This ensures that the app | 
 |     // does not immediately get suspended when this function returns. | 
 |     suspend_deferral_ = args->SuspendingOperation->GetDeferral(); | 
 |  | 
 |     auto extended_resources_manager = | 
 |         shared::uwp::ExtendedResourcesManager::GetInstance(); | 
 |     bool is_safe_to_suspend = extended_resources_manager->IsSafeToSuspend(); | 
 |  | 
 |     // Request extended execution during which we will suspend the app and | 
 |     // release extended resources. | 
 |     auto session = ref new ExtendedExecutionSession(); | 
 |     session->Reason = ExtendedExecutionReason::SavingData; | 
 |     session->Description = "Suspending..."; | 
 |  | 
 |     // If the extended execution session gets revoked, we have to complete | 
 |     // the deferral. | 
 |     Windows::Foundation::EventRegistrationToken revoked_token = | 
 |         session->Revoked += | 
 |         ref new TypedEventHandler<Object ^, | 
 |                                   ExtendedExecutionRevokedEventArgs ^>( | 
 |             this, &App::ExtendedExecutionSessionRevoked); | 
 |     Concurrency::create_task(session->RequestExtensionAsync()) | 
 |         .then([this, is_safe_to_suspend, | 
 |                extended_resources_manager](ExtendedExecutionResult result) { | 
 |           // Suspend the app and release extended resources during the extended | 
 |           // session. | 
 |  | 
 |           // Note if we dispatch "suspend" here before pause, application.cc | 
 |           // will inject the "pause" which will cause us to go async which | 
 |           // will cause us to not have completed the suspend operation before | 
 |           // returning, which UWP requires. | 
 |           ApplicationUwp::Get()->DispatchAndDelete( | 
 |               new ApplicationUwp::Event(kSbEventTypeBlur, NULL, NULL)); | 
 |           ApplicationUwp::Get()->DispatchAndDelete( | 
 |               new ApplicationUwp::Event(kSbEventTypeConceal, NULL, NULL)); | 
 |           ApplicationUwp::Get()->DispatchAndDelete( | 
 |               new ApplicationUwp::Event(kSbEventTypeFreeze, NULL, NULL)); | 
 |           extended_resources_manager->ReleaseExtendedResources(); | 
 |           if (!extended_resources_manager->IsSafeToSuspend()) { | 
 |             ForceQuit(); | 
 |           } | 
 |         }) | 
 |         .then([this, is_safe_to_suspend, session, revoked_token]() { | 
 |           // The extended session has completed, we are ready to be suspended | 
 |           // now. | 
 |           session->Revoked -= revoked_token; | 
 |           delete session; | 
 |  | 
 |           CompleteSuspendDeferral(); | 
 |         }); | 
 |     if (!is_safe_to_suspend) { | 
 |       ForceQuit(); | 
 |     } | 
 |   } | 
 |  | 
 |   void OnResuming(Platform::Object ^ sender, Platform::Object ^ args) { | 
 |     SB_LOG(INFO) << "Resuming application."; | 
 |     ApplicationUwp::Get()->DispatchAndDelete( | 
 |         new ApplicationUwp::Event(kSbEventTypeUnfreeze, NULL, NULL)); | 
 |     ApplicationUwp::Get()->DispatchAndDelete( | 
 |         new ApplicationUwp::Event(kSbEventTypeReveal, NULL, NULL)); | 
 |     ApplicationUwp::Get()->DispatchAndDelete( | 
 |         new ApplicationUwp::Event(kSbEventTypeFocus, NULL, NULL)); | 
 |     shared::win32::DrmSystemOnUwpResume(); | 
 |     shared::uwp::ExtendedResourcesManager::GetInstance() | 
 |         ->AcquireExtendedResources(); | 
 |   } | 
 |  | 
 |   void OnKeyUp(CoreWindow ^ sender, KeyEventArgs ^ args) { | 
 |     ApplicationUwp::Get()->OnKeyEvent(sender, args, true); | 
 |   } | 
 |  | 
 |   void OnKeyDown(CoreWindow ^ sender, KeyEventArgs ^ args) { | 
 |     ApplicationUwp::Get()->OnKeyEvent(sender, args, false); | 
 |   } | 
 |  | 
 |   void OnActivated(CoreApplicationView ^ application_view, | 
 |                    IActivatedEventArgs ^ args) { | 
 |     SB_LOG(INFO) << "OnActivated"; | 
 |     shared::uwp::ExtendedResourcesManager::GetInstance() | 
 |         ->AcquireExtendedResources(); | 
 |  | 
 |     std::string start_url = entry_point_; | 
 |     bool command_line_set = false; | 
 |  | 
 |     // Please see application lifecycle description: | 
 |     // https://docs.microsoft.com/en-us/windows/uwp/launch-resume/app-lifecycle | 
 |     // Note that this document was written for Xaml apps not core apps, | 
 |     // so for us the precise API is a little different. | 
 |     // The substance is that, while OnActivated is definitely called the | 
 |     // first time the application is started, it may additionally called | 
 |     // in other cases while the process is already running. Starboard | 
 |     // applications cannot fully restart in a process lifecycle, | 
 |     // so we interpret the first activation and the subsequent ones differently. | 
 |     if (args->Kind == ActivationKind::Protocol) { | 
 |       Uri ^ uri = dynamic_cast<IProtocolActivatedEventArgs ^>(args)->Uri; | 
 |  | 
 | #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) | 
 |       // The starboard: scheme provides commandline arguments, but that's | 
 |       // only allowed during a process's first activation. | 
 |       std::string scheme = platformStringToString(uri->SchemeName); | 
 |       if (!previously_activated_ && ends_with(scheme, "-starboard")) { | 
 |         std::string uri_string = wchar_tToUTF8(uri->RawUri->Data()); | 
 |         // args_ is a vector of std::string, but argv_ is a vector of | 
 |         // char* into args_ so as to compose a char**. | 
 |         args_ = ParseStarboardUri(uri_string); | 
 |         for (const std::string& arg : args_) { | 
 |           argv_.push_back(arg.c_str()); | 
 |         } | 
 |  | 
 |         ApplicationUwp::Get()->SetCommandLine(static_cast<int>(argv_.size()), | 
 |                                               argv_.data()); | 
 |         command_line_set = true; | 
 |       } | 
 | #endif  // defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) | 
 |       if (uri->SchemeName->Equals("youtube") || | 
 |           uri->SchemeName->Equals("youtube-tv") || | 
 |           uri->SchemeName->Equals("ms-xbl-07459769")) { | 
 |         std::string uri_string = platformStringToString(uri->RawUri); | 
 |  | 
 |         // Strip the protocol from the uri. | 
 |         size_t index = uri_string.find(':'); | 
 |         if (index != std::string::npos) { | 
 |           uri_string = uri_string.substr(index + 1); | 
 |         } | 
 |  | 
 |         ProcessDeepLinkUri(&uri_string); | 
 |       } | 
 |     } else if (args->Kind == ActivationKind::DialReceiver) { | 
 |       DialReceiverActivatedEventArgs ^ dial_args = | 
 |           dynamic_cast<DialReceiverActivatedEventArgs ^>(args); | 
 |       SB_CHECK(dial_args); | 
 |       Platform::String ^ arguments = dial_args->Arguments; | 
 |       if (previously_activated_) { | 
 |         std::string uri_string = | 
 |             kDialParamPrefix + platformStringToString(arguments); | 
 |         ProcessDeepLinkUri(&uri_string); | 
 |       } else { | 
 |         std::string activation_args = "--url="; | 
 |         activation_args.append(start_url); | 
 |         activation_args.append("?"); | 
 |         activation_args.append(platformStringToString(arguments)); | 
 |         SB_DLOG(INFO) << "Dial Activation url: " << activation_args; | 
 |         args_.push_back(GetArgvZero()); | 
 |         args_.push_back(activation_args); | 
 |         // Set partition URL in case start_url is the main app first run | 
 |         // special case. | 
 |         std::string partition_arg = "--local_storage_partition_url="; | 
 |         partition_arg.append(entry_point_); | 
 |         args_.push_back(partition_arg); | 
 |         for (const std::string& arg : args_) { | 
 |           argv_.push_back(arg.c_str()); | 
 |         } | 
 |         ApplicationUwp::Get()->SetCommandLine(static_cast<int>(argv_.size()), | 
 |                                               argv_.data()); | 
 |         command_line_set = true; | 
 |       } | 
 |     } | 
 |     previous_activation_kind_ = args->Kind; | 
 |  | 
 |     if (!previously_activated_) { | 
 |       if (!command_line_set) { | 
 | #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) | 
 |         TryAddCommandArgsFromStarboardFile(&args_); | 
 |         CommandLine cmd_line(args_); | 
 |         if (cmd_line.HasSwitch(kNetArgsCommandSwitchWait)) { | 
 |           SbTime timeout = kSbTimeSecond * 2; | 
 |           std::string val = cmd_line.GetSwitchValue(kNetArgsCommandSwitchWait); | 
 |           if (!val.empty()) { | 
 |             timeout = atoi(val.c_str()); | 
 |           } | 
 |           AddCommandArgsFromNetArgs(timeout, &args_); | 
 |         } | 
 | #endif  // defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) | 
 |  | 
 |         if (!CommandLine(args_).HasSwitch("url")) { | 
 |           args_.push_back(GetArgvZero()); | 
 |           std::string start_url_arg = "--url="; | 
 |           start_url_arg.append(start_url); | 
 |           args_.push_back(start_url_arg); | 
 |           std::string partition_arg = "--local_storage_partition_url="; | 
 |           partition_arg.append(entry_point_); | 
 |           args_.push_back(partition_arg); | 
 |         } | 
 |  | 
 |         for (auto& arg : args_) { | 
 |           argv_.push_back(arg.c_str()); | 
 |         } | 
 |  | 
 |         ApplicationUwp::Get()->SetCommandLine(static_cast<int>(argv_.size()), | 
 |                                               argv_.data()); | 
 |       } | 
 |  | 
 |       ApplicationUwp* application_uwp = ApplicationUwp::Get(); | 
 |       const CommandLine* command_line = application_uwp->GetCommandLine(); | 
 |  | 
 | #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) | 
 |       if (command_line->HasSwitch(kWatchDogLog)) { | 
 |         // Launch a thread. | 
 |         std::string switch_val = command_line->GetSwitchValue(kWatchDogLog); | 
 |         auto uwp_dir = | 
 |             Windows::Storage::ApplicationData::Current->LocalCacheFolder; | 
 |         std::stringstream ss; | 
 |         ss << platformStringToString(uwp_dir->Path) << "/" << switch_val; | 
 |         shared::uwp::StartWatchdogLog(ss.str()); | 
 |       } | 
 |  | 
 |       if (command_line->HasSwitch(kNetLogCommandSwitchWait)) { | 
 |         SbTime timeout = kSbTimeSecond; | 
 |         std::string val = | 
 |             command_line->GetSwitchValue(kNetLogCommandSwitchWait); | 
 |         if (!val.empty()) { | 
 |           timeout = atoi(val.c_str()); | 
 |         } | 
 |         NetLogWaitForClientConnected(timeout); | 
 |       } | 
 |  | 
 |       if (command_line->HasSwitch(kLogPathSwitch)) { | 
 |         std::stringstream ss; | 
 |         ss << platformStringToString( | 
 |             Windows::Storage::ApplicationData::Current->LocalCacheFolder->Path); | 
 |         ss << "\\" | 
 |            << "" << command_line->GetSwitchValue(kLogPathSwitch); | 
 |         std::string full_path_log_file = ss.str(); | 
 |         shared::uwp::OpenLogFileWin32(full_path_log_file.c_str()); | 
 |       } else { | 
 | #if !defined(COBALT_BUILD_TYPE_GOLD) | 
 |         // Log to a file on the last removable device available (probably the | 
 |         // most recently added removable device). | 
 |         if (KnownFolders::RemovableDevices != nullptr) { | 
 |           concurrency::create_task( | 
 |               KnownFolders::RemovableDevices->GetFoldersAsync()) | 
 |               .then( | 
 |                   [](concurrency::task<IVectorView<StorageFolder ^> ^> result) { | 
 |                     IVectorView<StorageFolder ^> ^ results; | 
 |                     try { | 
 |                       results = result.get(); | 
 |                     } catch (Platform::Exception ^) { | 
 |                       SB_LOG(ERROR) | 
 |                           << "Unable to open log file in RemovableDevices"; | 
 |                       return; | 
 |                     } | 
 |  | 
 |                     if (results->Size == 0) { | 
 |                       return; | 
 |                     } | 
 |  | 
 |                     StorageFolder ^ folder = results->GetAt(results->Size - 1); | 
 |                     Calendar ^ now = ref new Calendar(); | 
 |                     char filename[128]; | 
 |                     SbStringFormatF(filename, sizeof(filename), | 
 |                                     "cobalt_log_%04d%02d%02d_%02d%02d%02d.txt", | 
 |                                     now->Year, now->Month, now->Day, | 
 |                                     now->Hour + now->FirstHourInThisPeriod, | 
 |                                     now->Minute, now->Second); | 
 |                     shared::uwp::OpenLogFileUWP(folder, filename); | 
 |                   }); | 
 |         } | 
 | #endif  // !defined(COBALT_BUILD_TYPE_GOLD) | 
 |       } | 
 | #endif  // defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) | 
 |       SB_LOG(INFO) << "Starting " << GetBinaryName(); | 
 |  | 
 |       CoreWindow::GetForCurrentThread()->Activate(); | 
 |       // Call DispatchStart async so the UWP system thinks we're activated. | 
 |       // Some tools seem to want the application to be activated before | 
 |       // interacting with them, some things are disallowed during activation | 
 |       // (such as exiting), and DispatchStart (for example) runs | 
 |       // automated tests synchronously. | 
 |       RunInMainThreadAsync([this]() { | 
 |         ApplicationUwp::Get()->DispatchStart(application_start_time_); | 
 |       }); | 
 |     } | 
 |     previously_activated_ = true; | 
 |   } | 
 |  | 
 |  private: | 
 |   void ProcessDeepLinkUri(std::string* uri_string) { | 
 |     SB_DCHECK(uri_string); | 
 |     if (previously_activated_) { | 
 |       std::unique_ptr<Application::Event> event = | 
 |           MakeDeepLinkEvent(*uri_string); | 
 |       SB_DCHECK(event); | 
 |       ApplicationUwp::Get()->Inject(event.release()); | 
 |     } else { | 
 |       ApplicationUwp::Get()->SetStartLink(uri_string->c_str()); | 
 |     } | 
 |   } | 
 |  | 
 |   std::string entry_point_; | 
 |   bool is_online_; | 
 |   bool previously_activated_; | 
 |   // Only valid if previously_activated_ is true | 
 |   ActivationKind previous_activation_kind_; | 
 |   std::vector<std::string> args_; | 
 |   std::vector<const char*> argv_; | 
 |  | 
 |   shared::uwp::ApplicationUwp application_; | 
 |   SuspendingDeferral ^ suspend_deferral_ = nullptr; | 
 |   SbTimeMonotonic application_start_time_; | 
 | }; | 
 |  | 
 | ref class Direct3DApplicationSource sealed : IFrameworkViewSource { | 
 |  public: | 
 |   Direct3DApplicationSource(SbTimeMonotonic start_time) | 
 |       : application_start_time_{start_time} {} | 
 |   virtual IFrameworkView ^ CreateView() { | 
 |     return ref new App(application_start_time_); | 
 |   } private : SbTimeMonotonic application_start_time_; | 
 | }; | 
 |  | 
 | namespace shared { | 
 | namespace uwp { | 
 |  | 
 | void DisplayStatusWatcher::CreateWatcher() { | 
 |   Platform::Collections::Vector<Platform::String ^> ^ requestedProperties = | 
 |       ref new Platform::Collections::Vector<Platform::String ^>(); | 
 |   requestedProperties->Append("System.Devices.InterfaceClassGuid"); | 
 |   requestedProperties->Append("System.ItemNameDisplay"); | 
 |   watcher_ = DeviceInformation::CreateWatcher( | 
 |       const_cast<Platform::String ^>(kGenericPnpMonitorAqs), | 
 |       requestedProperties); | 
 |   SB_CHECK(watcher_); | 
 |   watcher_->Added += | 
 |       ref new TypedEventHandler<DeviceWatcher ^, DeviceInformation ^>( | 
 |           &OnDeviceAdded); | 
 |   watcher_->Removed += | 
 |       ref new TypedEventHandler<DeviceWatcher ^, DeviceInformationUpdate ^>( | 
 |           &OnDeviceRemoved); | 
 |   watcher_->Start(); | 
 | } | 
 |  | 
 | bool DisplayStatusWatcher::IsWatcherStarted() { | 
 |   SB_CHECK(watcher_); | 
 |   DeviceWatcherStatus status = watcher_->Status; | 
 |   return (status == DeviceWatcherStatus::Started) || | 
 |          (status == DeviceWatcherStatus::EnumerationCompleted); | 
 | } | 
 |  | 
 | void DisplayStatusWatcher::StopWatcher() { | 
 |   SB_CHECK(watcher_); | 
 |   if (IsWatcherStarted()) { | 
 |     watcher_->Stop(); | 
 |   } | 
 | } | 
 |  | 
 | #if SB_API_VERSION >= 15 | 
 | ApplicationUwp::ApplicationUwp(SbEventHandleCallback sb_event_handle_callback) | 
 |     : shared::starboard::Application(sb_event_handle_callback), | 
 |       window_(kSbWindowInvalid), | 
 | #else | 
 | ApplicationUwp::ApplicationUwp() | 
 |     : window_(kSbWindowInvalid), | 
 | #endif  // SB_API_VERSION >= 15 | 
 |       localized_strings_(SbSystemGetLocaleId()), | 
 |       device_id_(MakeDeviceId()) { | 
 |   SbWindowOptions options; | 
 |   SbWindowSetDefaultOptions(&options); | 
 |   window_size_ = options.size; | 
 |   analog_thumbstick_thread_.reset(new AnalogThumbstickThread(this)); | 
 | } | 
 |  | 
 | ApplicationUwp::~ApplicationUwp() { | 
 |   SB_CHECK(watcher_); | 
 |   { | 
 |     ScopedLock lock(time_event_mutex_); | 
 |     timer_event_map_.clear(); | 
 |   } | 
 |   watcher_->StopWatcher(); | 
 |   analog_thumbstick_thread_.reset(nullptr); | 
 | } | 
 |  | 
 | void ApplicationUwp::Initialize() {} | 
 |  | 
 | void ApplicationUwp::Teardown() { | 
 |   CloseWatchdogLog(); | 
 | } | 
 |  | 
 | Application::Event* ApplicationUwp::GetNextEvent() { | 
 |   SB_NOTREACHED(); | 
 |   return nullptr; | 
 | } | 
 |  | 
 | SbWindow ApplicationUwp::CreateWindowForUWP(const SbWindowOptions*) { | 
 |   // TODO: Determine why SB_DCHECK(IsCurrentThread()) fails in nplb, fix it, | 
 |   // and add back this check. | 
 |  | 
 |   if (SbWindowIsValid(window_)) { | 
 |     return kSbWindowInvalid; | 
 |   } | 
 |  | 
 |   if (!watcher_) { | 
 |     watcher_ = ref new shared::uwp::DisplayStatusWatcher(); | 
 |     watcher_->CreateWatcher(); | 
 |   } | 
 |  | 
 |   window_size_ = GetPreferredWindowSize(); | 
 |  | 
 |   window_ = new SbWindowPrivate(window_size_.width, window_size_.height); | 
 |   return window_; | 
 | } | 
 |  | 
 | SbWindowSize ApplicationUwp::GetVisibleAreaSize() { | 
 |   SbWindowSize size{}; | 
 |   if (SbWindowGetSize(window_, &size)) { | 
 |     return size; | 
 |   } | 
 |   return window_size_; | 
 | } | 
 |  | 
 | SbWindowSize ApplicationUwp::GetPreferredWindowSize() { | 
 |   bool scale_enabled = true; | 
 |   if (ApiInformation::IsApiContractPresent( | 
 |           "Windows.UI.ViewManagement.ViewManagementViewScalingContract", 1)) { | 
 |     // Use unscaled layout where possible | 
 |     scale_enabled = !ApplicationViewScaling::TrySetDisableLayoutScaling(true); | 
 |   } | 
 |  | 
 |   auto app_view = ApplicationView::GetForCurrentView(); | 
 |   bool is_fullscreen = app_view->IsFullScreenMode; | 
 |  | 
 |   if (!is_fullscreen && scale_enabled && | 
 |       ApiInformation::IsApiContractPresent( | 
 |           "Windows.Xbox.ApplicationResourcesContract", 1)) { | 
 |     // Always use full screen mode for XBox | 
 |     is_fullscreen = app_view->TryEnterFullScreenMode(); | 
 |   } | 
 |  | 
 |   UpdateDisplayPreferredMode(); | 
 |   int width = 0; | 
 |   int height = 0; | 
 |   auto display_mode = ApplicationUwp::Get()->GetPreferredModeHdr(); | 
 |   if (!display_mode) { | 
 |     display_mode = ApplicationUwp::Get()->GetPreferredModeHdmi(); | 
 |   } | 
 |   if (display_mode) { | 
 |     width = display_mode->ResolutionWidthInRawPixels; | 
 |     height = display_mode->ResolutionHeightInRawPixels; | 
 |   } else { | 
 |     auto bounds = app_view->VisibleBounds; | 
 |     auto scaleFactor = | 
 |         DisplayInformation::GetForCurrentView()->RawPixelsPerViewPixel; | 
 |     width = bounds.Width * scaleFactor; | 
 |     height = bounds.Height * scaleFactor; | 
 |   } | 
 |   SB_LOG(INFO) << "Preferred window size is " << width << " x " << height | 
 |                << "\nScaling is " << (scale_enabled ? "enabled" : "disabled") | 
 |                << "\nFull Screen mode is " << (is_fullscreen ? "ON" : "OFF"); | 
 |  | 
 |   return SbWindowSize{width, height}; | 
 | } | 
 |  | 
 | bool ApplicationUwp::DestroyWindow(SbWindow window) { | 
 |   // TODO: Determine why SB_DCHECK(IsCurrentThread()) fails in nplb, fix it, | 
 |   // and add back this check. | 
 |  | 
 |   if (!SbWindowIsValid(window)) { | 
 |     SB_DLOG(ERROR) << __FUNCTION__ << ": Invalid context."; | 
 |     return false; | 
 |   } | 
 |  | 
 |   auto app_view = ApplicationView::GetForCurrentView(); | 
 |   if (app_view->IsFullScreenMode && | 
 |       ApiInformation::IsApiContractPresent( | 
 |           "Windows.Xbox.ApplicationResourcesContract", 1)) { | 
 |     app_view->ExitFullScreenMode(); | 
 |   } | 
 |  | 
 |   SB_DCHECK(window_ == window); | 
 |   delete window; | 
 |   window_ = kSbWindowInvalid; | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | void ApplicationUwp::UpdateDisplayPreferredMode() { | 
 |   ScopedLock lock(preferred_display_mode_mutex_); | 
 |   preferred_display_mode_hdmi_ = nullptr; | 
 |   preferred_display_mode_hdr_ = nullptr; | 
 |   if (!ApiInformation::IsTypePresent( | 
 |           "Windows.Graphics.Display.Core.HdmiDisplayInformation")) { | 
 |     return; | 
 |   } | 
 |  | 
 |   auto hdmi_display_info = HdmiDisplayInformation::GetForCurrentView(); | 
 |   if (!hdmi_display_info) { | 
 |     return; | 
 |   } | 
 |  | 
 |   preferred_display_mode_hdmi_ = hdmi_display_info->GetCurrentDisplayMode(); | 
 |   for (auto mode : hdmi_display_info->GetSupportedDisplayModes()) { | 
 |     if (mode->ResolutionWidthInRawPixels == | 
 |             preferred_display_mode_hdmi_->ResolutionWidthInRawPixels && | 
 |         mode->ResolutionHeightInRawPixels == | 
 |             preferred_display_mode_hdmi_->ResolutionHeightInRawPixels && | 
 |         mode->Is2086MetadataSupported && mode->IsSmpte2084Supported && | 
 |         mode->BitsPerPixel >= kYuv420BitsPerPixelForHdr10Mode && | 
 |         mode->ColorSpace == HdmiDisplayColorSpace::BT2020) { | 
 |       if (!preferred_display_mode_hdr_ || | 
 |           preferred_display_mode_hdr_->RefreshRate < mode->RefreshRate) { | 
 |         preferred_display_mode_hdr_ = mode; | 
 |       } | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | bool ApplicationUwp::DispatchNextEvent() { | 
 |   core_window_->Activate(); | 
 |   core_window_->Dispatcher->ProcessEvents( | 
 |       CoreProcessEventsOption::ProcessUntilQuit); | 
 |   return false; | 
 | } | 
 |  | 
 | Windows::Media::Protection::HdcpSession ^ ApplicationUwp::GetHdcpSession() { | 
 |   if (!hdcp_session_) { | 
 |     hdcp_session_ = ref new HdcpSession(); | 
 |   } | 
 |   return hdcp_session_; | 
 | } | 
 |  | 
 | void ApplicationUwp::ResetHdcpSession() { | 
 |   // delete will call the destructor, but not free memory. | 
 |   // The destructor is called explicitly so that HDCP session can be | 
 |   // torn down immediately. | 
 |   if (hdcp_session_) { | 
 |     delete hdcp_session_; | 
 |     hdcp_session_ = nullptr; | 
 |   } | 
 | } | 
 |  | 
 | void ApplicationUwp::Inject(Application::Event* event) { | 
 |   RunInMainThreadAsync([this, event]() { | 
 |     bool result = DispatchAndDelete(event); | 
 |     if (!result) { | 
 |       NetLogFlushThenClose(); | 
 |       CoreApplication::Exit(); | 
 |     } | 
 |   }); | 
 | } | 
 |  | 
 | void ApplicationUwp::InjectKeypress(SbKey key) { | 
 |   if (!SbWindowIsValid(window_)) { | 
 |     return; | 
 |   } | 
 |   std::unique_ptr<SbInputData> press_data(new SbInputData()); | 
 |   std::unique_ptr<SbInputData> unpress_data(new SbInputData()); | 
 |  | 
 |   memset(press_data.get(), 0, sizeof(*press_data)); | 
 |   press_data->window = window_; | 
 |   press_data->device_type = kSbInputDeviceTypeKeyboard; | 
 |   press_data->device_id = device_id(); | 
 |   press_data->key = key; | 
 |   press_data->type = kSbInputEventTypePress; | 
 |  | 
 |   *unpress_data = *press_data; | 
 |   unpress_data->type = kSbInputEventTypeUnpress; | 
 |  | 
 |   Inject(new Event(kSbEventTypeInput, press_data.release(), | 
 |                    &Application::DeleteDestructor<SbInputData>)); | 
 |   Inject(new Event(kSbEventTypeInput, unpress_data.release(), | 
 |                    &Application::DeleteDestructor<SbInputData>)); | 
 | } | 
 |  | 
 | void ApplicationUwp::InjectTimedEvent(Application::TimedEvent* timed_event) { | 
 |   SbTimeMonotonic delay_usec = | 
 |       timed_event->target_time - SbTimeGetMonotonicNow(); | 
 |   if (delay_usec < 0) { | 
 |     delay_usec = 0; | 
 |   } | 
 |  | 
 |   // TimeSpan ticks are, like FILETIME, 100ns | 
 |   const SbTimeMonotonic kTicksPerUsec = 10; | 
 |  | 
 |   TimeSpan timespan; | 
 |   timespan.Duration = delay_usec * kTicksPerUsec; | 
 |  | 
 |   ScopedLock lock(time_event_mutex_); | 
 |   ThreadPoolTimer ^ timer = ThreadPoolTimer::CreateTimer( | 
 |       ref new TimerElapsedHandler([this, timed_event](ThreadPoolTimer ^ timer) { | 
 |         RunInMainThreadAsync([this, timed_event]() { | 
 |           // Even if the event is canceled, the callback can still fire. | 
 |           // Thus, the existence of event in timer_event_map_ is used | 
 |           // as a source of truth. | 
 |           std::size_t number_erased = 0; | 
 |           { | 
 |             ScopedLock lock(time_event_mutex_); | 
 |             number_erased = timer_event_map_.erase(timed_event->id); | 
 |           } | 
 |           if (number_erased > 0) { | 
 |             timed_event->callback(timed_event->context); | 
 |           } | 
 |         }); | 
 |       }), | 
 |       timespan); | 
 |   timer_event_map_.emplace(timed_event->id, timer); | 
 | } | 
 |  | 
 | void ApplicationUwp::CancelTimedEvent(SbEventId event_id) { | 
 |   ScopedLock lock(time_event_mutex_); | 
 |   auto it = timer_event_map_.find(event_id); | 
 |   if (it == timer_event_map_.end()) { | 
 |     return; | 
 |   } | 
 |   it->second->Cancel(); | 
 |   timer_event_map_.erase(it); | 
 | } | 
 |  | 
 | Application::TimedEvent* ApplicationUwp::GetNextDueTimedEvent() { | 
 |   SB_NOTIMPLEMENTED(); | 
 |   return nullptr; | 
 | } | 
 |  | 
 | SbTimeMonotonic ApplicationUwp::GetNextTimedEventTargetTime() { | 
 |   SB_NOTIMPLEMENTED(); | 
 |   return 0; | 
 | } | 
 |  | 
 | void ApplicationUwp::OnJoystickUpdate(SbKey key, SbInputVector input_vector) { | 
 |   if (!SbWindowIsValid(window_)) { | 
 |     return; | 
 |   } | 
 |   scoped_ptr<SbInputData> data(new SbInputData()); | 
 |   memset(data.get(), 0, sizeof(*data)); | 
 |   data->window = window_; | 
 |   data->type = kSbInputEventTypeMove; | 
 |   data->device_type = kSbInputDeviceTypeGamepad; | 
 |   data->device_id = device_id(); | 
 |   data->key = key; | 
 |   data->character = 0; | 
 |  | 
 |   data->key_modifiers = kSbKeyModifiersNone; | 
 |   data->position = input_vector; | 
 |  | 
 |   SbKeyLocation key_location = kSbKeyLocationUnspecified; | 
 |   switch (key) { | 
 |     case kSbKeyGamepadLeftStickLeft: | 
 |     case kSbKeyGamepadLeftStickUp: { | 
 |       key_location = kSbKeyLocationLeft; | 
 |       break; | 
 |     } | 
 |     case kSbKeyGamepadRightStickLeft: | 
 |     case kSbKeyGamepadRightStickUp: { | 
 |       key_location = kSbKeyLocationRight; | 
 |       break; | 
 |     } | 
 |     default: { | 
 |       SB_NOTREACHED(); | 
 |       break; | 
 |     } | 
 |   } | 
 |  | 
 |   data->key_location = key_location; | 
 |   Inject(new Event(kSbEventTypeInput, data.release(), | 
 |                    &Application::DeleteDestructor<SbInputData>)); | 
 | } | 
 |  | 
 | Platform::String ^ | 
 |     ApplicationUwp::GetString(const char* id, const char* fallback) const { | 
 |   return stringToPlatformString(localized_strings_.GetString(id, fallback)); | 
 | } | 
 |  | 
 | bool ApplicationUwp::IsHdcpOn() { | 
 |   ScopedLock lock(hdcp_session_mutex_); | 
 |  | 
 |   return GetHdcpSession()->IsEffectiveProtectionAtLeast(kHDCPProtectionMode); | 
 | } | 
 |  | 
 | bool ApplicationUwp::TurnOnHdcp() { | 
 |   HdcpSetProtectionResult protection_result; | 
 |   { | 
 |     ScopedLock lock(hdcp_session_mutex_); | 
 |  | 
 |     protection_result = WaitForResult( | 
 |         GetHdcpSession()->SetDesiredMinProtectionAsync(kHDCPProtectionMode)); | 
 |   } | 
 |  | 
 |   if (IsHdcpOn()) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   // If the operation did not have intended result, log something. | 
 |   switch (protection_result) { | 
 |     case HdcpSetProtectionResult::Success: | 
 |       SB_LOG(INFO) << "Successfully set HDCP."; | 
 |       break; | 
 |     case HdcpSetProtectionResult::NotSupported: | 
 |       SB_LOG(INFO) << "HDCP is not supported."; | 
 |       break; | 
 |     case HdcpSetProtectionResult::TimedOut: | 
 |       SB_LOG(INFO) << "Setting HDCP timed out."; | 
 |       break; | 
 |     case HdcpSetProtectionResult::UnknownFailure: | 
 |       SB_LOG(INFO) << "Unknown failure returned while setting HDCP."; | 
 |       break; | 
 |   } | 
 |  | 
 |   return false; | 
 | } | 
 |  | 
 | bool ApplicationUwp::TurnOffHdcp() { | 
 |   { | 
 |     ScopedLock lock(hdcp_session_mutex_); | 
 |     ResetHdcpSession(); | 
 |   } | 
 |   bool success = !IsHdcpOn(); | 
 |   return success; | 
 | } | 
 |  | 
 | // TODO: Consolidate this function with TurnOfHdcp() and TurnOffHdcp(). | 
 | bool ApplicationUwp::SetOutputProtection(bool should_enable_dhcp) { | 
 |   bool is_hdcp_on = IsHdcpOn(); | 
 |  | 
 |   if (is_hdcp_on == should_enable_dhcp) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   SB_LOG(INFO) << "Attempting to " | 
 |                << (should_enable_dhcp ? "enable" : "disable") | 
 |                << " output protection.  Current status: " | 
 |                << (is_hdcp_on ? "enabled" : "disabled"); | 
 |   SbTimeMonotonic tick = SbTimeGetMonotonicNow(); | 
 |  | 
 |   bool hdcp_success = false; | 
 |   if (should_enable_dhcp) { | 
 |     hdcp_success = TurnOnHdcp(); | 
 |   } else { | 
 |     hdcp_success = TurnOffHdcp(); | 
 |   } | 
 |  | 
 |   is_hdcp_on = (hdcp_success ? should_enable_dhcp : !should_enable_dhcp); | 
 |  | 
 |   SbTimeMonotonic tock = SbTimeGetMonotonicNow(); | 
 |   SB_LOG(INFO) << "Output protection is " | 
 |                << (is_hdcp_on ? "enabled" : "disabled") | 
 |                << ".  Toggling HDCP took " << (tock - tick) / kSbTimeMillisecond | 
 |                << " milliseconds."; | 
 |   return hdcp_success; | 
 | } | 
 |  | 
 | Platform::Agile<Windows::UI::Core::CoreDispatcher> GetDispatcher() { | 
 |   return ApplicationUwp::Get()->GetDispatcher(); | 
 | } | 
 |  | 
 | Platform::Agile<Windows::Media::SystemMediaTransportControls> | 
 | GetTransportControls() { | 
 |   return ApplicationUwp::Get()->GetTransportControls(); | 
 | } | 
 |  | 
 | void DisplayRequestActive() { | 
 |   RunInMainThreadAsync([]() { | 
 |     if (display_request != nullptr) { | 
 |       display_request->RequestActive(); | 
 |     } | 
 |   }); | 
 | } | 
 |  | 
 | void DisplayRequestRelease() { | 
 |   RunInMainThreadAsync([]() { | 
 |     if (display_request != nullptr) { | 
 |       display_request->RequestRelease(); | 
 |     } | 
 |   }); | 
 | } | 
 |  | 
 | void InjectKeypress(SbKey key) { | 
 |   ApplicationUwp::Get()->InjectKeypress(key); | 
 | } | 
 |  | 
 | namespace { | 
 |  | 
 | // Calls CoreApplication::Run() on a new thread to free up the main thread. | 
 | // This allows all extended resources related operations to be run on the main | 
 | // thread. | 
 | class CoreApplicationThread : public ::starboard::Thread { | 
 |  public: | 
 |   explicit CoreApplicationThread(SbTimeMonotonic start_time) | 
 |       : application_start_time_{start_time}, Thread("core_app") {} | 
 |   void Run() override { | 
 |     CoreApplication::Run( | 
 |         ref new Direct3DApplicationSource(application_start_time_)); | 
 |     ExtendedResourcesManager::GetInstance()->Quit(); | 
 |   } | 
 |  | 
 |  private: | 
 |   SbTimeMonotonic application_start_time_; | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | int InternalMain() { | 
 |   volatile auto start_time = SbTimeGetMonotonicNow(); | 
 |   if (!IsDebuggerPresent()) { | 
 |     // By default, a Windows application will display a dialog box | 
 |     // when it crashes. This is extremely undesirable when run offline. | 
 |     // The following configures messages to be print to the console instead. | 
 |     _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); | 
 |     _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); | 
 |     _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); | 
 |     _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); | 
 |   } | 
 |  | 
 |   WSAData wsaData; | 
 |   int init_result = WSAStartup( | 
 |       MAKEWORD(kWinSockVersionMajor, kWinSockVersionMajor), &wsaData); | 
 |  | 
 |   SB_CHECK(init_result == 0); | 
 |   // WSAStartup returns the highest version that is supported up to the version | 
 |   // we request. | 
 |   SB_CHECK(LOBYTE(wsaData.wVersion) == kWinSockVersionMajor && | 
 |            HIBYTE(wsaData.wVersion) == kWinSockVersionMinor); | 
 |  | 
 |   HRESULT hr = MFStartup(MF_VERSION); | 
 |   SB_DCHECK(SUCCEEDED(hr)); | 
 |  | 
 | #if defined(COBALT_BUILD_TYPE_GOLD) | 
 |   // Early exit for gold builds on desktop as a security measure. | 
 | #if SB_API_VERSION < 15 | 
 |   if (SbSystemGetDeviceType() == kSbSystemDeviceTypeDesktopPC) { | 
 |     return main_return_value; | 
 |   } | 
 | #else | 
 |   if (GetSystemPropertyString(kSbSystemPropertyDeviceType) == | 
 |       kSystemDeviceTypeDesktopPC) { | 
 |     return main_return_value; | 
 |   } | 
 | #endif | 
 | #endif  // defined(COBALT_BUILD_TYPE_GOLD) | 
 |  | 
 |   shared::win32::RegisterMainThread(); | 
 |  | 
 |   ExtendedResourcesManager extended_resources_manager; | 
 |   CoreApplicationThread thread(start_time); | 
 |  | 
 |   thread.Start(); | 
 |   extended_resources_manager.Run(); | 
 |  | 
 |   NetLogFlushThenClose(); | 
 |   CoreApplication::Exit(); | 
 |  | 
 |   MFShutdown(); | 
 |   WSACleanup(); | 
 |  | 
 |   return main_return_value; | 
 | } | 
 |  | 
 | #if SB_API_VERSION >= 15 | 
 | extern "C" int SbRunStarboardMain(int argc, | 
 |                                   char** argv, | 
 |                                   SbEventHandleCallback callback) { | 
 |   return ApplicationUwp::Get()->Run(argc, argv); | 
 | } | 
 | #endif  // SB_API_VERSION >= 15 | 
 |  | 
 | }  // namespace uwp | 
 | }  // namespace shared | 
 | }  // namespace starboard | 
 |  | 
 | [Platform::MTAThread] int main(Platform::Array<Platform::String ^> ^ args) { | 
 |   return starboard::shared::uwp::InternalMain(); | 
 | } |