blob: 1e76ebfee38f31c7463a6d1b6313a995783c91a6 [file] [log] [blame]
// 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/common/time.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;
const uint32_t kHdr4kRefreshRateMaximum = 60;
const uint32_t k4kResolutionWidth = 3840;
const uint32_t k4kResolutionHeight = 2160;
// 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(int64_t 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'000);
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(int64_t 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)) {
// Wait for net args is flaky and needs extended wait time on Xbox.
int64_t timeout_usec = 30'000'000; // 30 seconds
std::string val = cmd_line.GetSwitchValue(kNetArgsCommandSwitchWait);
if (!val.empty()) {
timeout_usec = atoi(val.c_str());
}
AddCommandArgsFromNetArgs(timeout_usec, &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)) {
int64_t timeout_usec = 1'000'000; // 1 second
std::string val =
command_line->GetSwitchValue(kNetLogCommandSwitchWait);
if (!val.empty()) {
timeout_usec = atoi(val.c_str());
}
NetLogWaitForClientConnected(timeout_usec);
}
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];
snprintf(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;
int64_t application_start_time_;
};
ref class Direct3DApplicationSource sealed : IFrameworkViewSource {
public:
Direct3DApplicationSource(int64_t start_time)
: application_start_time_{start_time} {}
virtual IFrameworkView ^ CreateView() {
return ref new App(application_start_time_);
} private : int64_t 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()) {
// Check that resolution matches the preferred display mode.
if (mode->ResolutionWidthInRawPixels !=
preferred_display_mode_hdmi_->ResolutionWidthInRawPixels ||
mode->ResolutionHeightInRawPixels !=
preferred_display_mode_hdmi_->ResolutionHeightInRawPixels) {
continue;
}
// Verify HDR metadata and transfer function are supported.
if (!mode->Is2086MetadataSupported || !mode->IsSmpte2084Supported) {
continue;
}
// Verify we have enough bits per pixel and the correct color space for HDR.
if (mode->BitsPerPixel < kYuv420BitsPerPixelForHdr10Mode ||
mode->ColorSpace != HdmiDisplayColorSpace::BT2020) {
continue;
}
// We don't serve 4k HDR videos over 60fps, skipping display modes that will
// consume more power than needed.
if (mode->ResolutionWidthInRawPixels >= k4kResolutionWidth &&
mode->ResolutionHeightInRawPixels >= k4kResolutionHeight &&
mode->RefreshRate > kHdr4kRefreshRateMaximum) {
continue;
}
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) {
int64_t delay_usec = timed_event->target_time - CurrentMonotonicTime();
if (delay_usec < 0) {
delay_usec = 0;
}
// TimeSpan ticks are, like FILETIME, 100ns
const int64_t 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;
}
int64_t 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");
int64_t tick = CurrentMonotonicTime();
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);
int64_t tock = CurrentMonotonicTime();
SB_LOG(INFO) << "Output protection is "
<< (is_hdcp_on ? "enabled" : "disabled")
<< ". Toggling HDCP took " << (tock - tick) / 1000
<< " 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(int64_t 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:
int64_t application_start_time_;
};
} // namespace
int InternalMain() {
volatile auto start_time = CurrentMonotonicTime();
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();
}