diff --git a/Common/ModalDialog.xaml.cpp b/Common/ModalDialog.xaml.cpp index f182f9cb..55bd8058 100644 Binary files a/Common/ModalDialog.xaml.cpp and b/Common/ModalDialog.xaml.cpp differ diff --git a/Package.appxmanifest b/Package.appxmanifest index 88b8acdc..1a8ccfc8 100644 --- a/Package.appxmanifest +++ b/Package.appxmanifest @@ -49,4 +49,4 @@ - + \ No newline at end of file diff --git a/Pages/AppPage.xaml.cpp b/Pages/AppPage.xaml.cpp index b2bacdf9..2558f454 100644 --- a/Pages/AppPage.xaml.cpp +++ b/Pages/AppPage.xaml.cpp @@ -2,6 +2,7 @@ #include "AppPage.Xaml.h" #include "Common\ModalDialog.xaml.h" #include "HostSettingsPage.xaml.h" +#include "HostSelectorPage.xaml.h" #include "State\MoonlightClient.h" #include "StreamPage.xaml.h" #include "Utils.hpp" @@ -24,6 +25,21 @@ using namespace Windows::UI::Composition; // The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238 +static MoonlightApp^ GetAppById(MoonlightHost^ host, int appId) { + if (host == nullptr) { + return nullptr; + } + + for (unsigned int i = 0; i < host->Apps->Size; ++i) { + auto app = host->Apps->GetAt(i); + if (app != nullptr && app->Id == appId) { + return app; + } + } + + return nullptr; +} + AppPage::AppPage() { InitializeComponent(); @@ -34,6 +50,7 @@ AppPage::AppPage() } void AppPage::OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) { + MoonlightHost^ mhost = dynamic_cast(e->Parameter); if (mhost == nullptr) return; host = mhost; @@ -136,9 +153,12 @@ void AppPage::Connect(int appId) { continueAppFetch.store(false); + MoonlightApp^ app = GetAppById(host, appId); + StreamConfiguration ^ config = ref new StreamConfiguration(); config->hostname = host->LastHostname; config->appID = appId; + config->appName = app ? app->Name : "App"; config->width = host->Resolution->Width; config->height = host->Resolution->Height; config->bitrate = host->Bitrate; @@ -154,7 +174,7 @@ void AppPage::Connect(int appId) { if (config->enableHDR) { host->VideoCodec = "HEVC (H.265)"; } - bool result = this->Frame->Navigate(Windows::UI::Xaml::Interop::TypeName(StreamPage::typeid), config); + bool result = this->Frame->Navigate(Windows::UI::Xaml::Interop::TypeName(StreamPage::typeid), config); if (!result) { printf("C"); } diff --git a/Pages/HostSelectorPage.xaml.cpp b/Pages/HostSelectorPage.xaml.cpp index 26214dbc..5a96ec39 100644 --- a/Pages/HostSelectorPage.xaml.cpp +++ b/Pages/HostSelectorPage.xaml.cpp @@ -303,14 +303,7 @@ void moonlight_xbox_dx::HostSelectorPage::wakeHostButton_Click(Platform::Object^ try { bool success = State->WakeHost(currentHost); - if (success) { - ContentDialog^ confirm = ref new ContentDialog(); - confirm->Title = "Wake Host"; - confirm->Content = "Wake-on-LAN packet sent successfully to " + currentHost->ComputerName; - confirm->PrimaryButtonText = "OK"; - concurrency::create_task(::moonlight_xbox_dx::ModalDialog::ShowOnceAsync(confirm)); - } - else { + if (!success) { ContentDialog^ fail = ref new ContentDialog(); fail->Title = "Wake Host Failed"; fail->Content = "Failed to send Wake-on-LAN packet.\n\nPlease check if Wake-on-LAN is enabled on the host."; diff --git a/Pages/StreamPage.xaml b/Pages/StreamPage.xaml index 9c5372e0..96b2529a 100644 --- a/Pages/StreamPage.xaml +++ b/Pages/StreamPage.xaml @@ -82,7 +82,8 @@ - + + diff --git a/Pages/StreamPage.xaml.cpp b/Pages/StreamPage.xaml.cpp index 70b4dd24..d751986a 100644 --- a/Pages/StreamPage.xaml.cpp +++ b/Pages/StreamPage.xaml.cpp @@ -71,50 +71,28 @@ void StreamPage::Page_Loaded(Platform::Object ^ sender, Windows::UI::Xaml::Route Utils::Log("StreamPage::Page_Loaded: SetSwapChainPanel failed\n"); } - // Defer heavy initialization so the handler returns and the UI can present. - // Use a low-priority dispatch so the framework can complete the first frame. Platform::WeakReference weakThis(this); - auto ignore = this->Dispatcher->RunAsync(CoreDispatcherPriority::Low, ref new DispatchedHandler([weakThis]() { + DISPATCH_UI([weakThis] { auto that = weakThis.Resolve(); if (that == nullptr) return; try { that->m_main = std::unique_ptr(new moonlight_xbox_dxMain(that->m_deviceResources, that, new MoonlightClient(), that->configuration)); - - DISPATCH_UI([that], { - try { - that->m_main->CreateDeviceDependentResources(); - that->m_main->CreateWindowSizeDependentResources(); - that->m_main->StartRenderLoop(); - } catch (...) { - Utils::Log("StreamPage: init failed\n"); - } - }); - - } catch (const std::exception &ex) { - Windows::UI::Xaml::Controls::ContentDialog ^ dialog = ref new Windows::UI::Xaml::Controls::ContentDialog(); - dialog->Content = Utils::StringPrintf(ex.what()); - dialog->CloseButtonText = L"OK"; - dialog->ShowAsync(); - } catch (const std::string &string) { - Windows::UI::Xaml::Controls::ContentDialog ^ dialog = ref new Windows::UI::Xaml::Controls::ContentDialog(); - dialog->Content = Utils::StringPrintf(string.c_str()); - dialog->CloseButtonText = L"OK"; - dialog->ShowAsync(); - } catch (Platform::Exception ^ e) { - Windows::UI::Xaml::Controls::ContentDialog ^ dialog = ref new Windows::UI::Xaml::Controls::ContentDialog(); - Platform::String ^ errorMsg = ref new Platform::String(); - errorMsg = errorMsg->Concat(L"Exception: ", e->Message); - errorMsg = errorMsg->Concat(errorMsg, Utils::StringPrintf("%x", e->HResult)); - dialog->Content = errorMsg; - dialog->CloseButtonText = L"OK"; - dialog->ShowAsync(); - } catch (...) { - Windows::UI::Xaml::Controls::ContentDialog ^ dialog = ref new Windows::UI::Xaml::Controls::ContentDialog(); - dialog->Content = L"Generic Exception"; - dialog->CloseButtonText = L"OK"; - dialog->ShowAsync(); - } - })); + that->m_main->CreateDeviceDependentResources(); + that->m_main->CreateWindowSizeDependentResources(); + that->m_main->StartRenderLoop(); + } catch (const std::exception &ex) { + Utils::Logf("StreamPage::Page_Loaded: Exception when starting stream. Exception: %s", ex.what()); + } catch (const std::string &string) { + Utils::Logf("StreamPage::Page_Loaded: Exception when starting stream. Exception: %s", string); + } catch (Platform::Exception ^ e) { + Platform::String ^ errorMsg = ref new Platform::String(); + errorMsg = errorMsg->Concat(L"Exception: ", e->Message); + errorMsg = errorMsg->Concat(errorMsg, Utils::StringPrintf("%x", e->HResult)); + Utils::Logf("StreamPage::Page_Loaded: Exception when starting stream. Exception: %s", Utils::PlatformStringToStdString(errorMsg)); + } catch (...) { + Utils::Log("StreamPage::Page_Loaded: Exception when starting stream. Exception: Generic Exception"); + } + }); } void StreamPage::Page_Unloaded(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e) { @@ -127,11 +105,13 @@ void StreamPage::Page_Unloaded(Platform::Object ^ sender, Windows::UI::Xaml::Rou try { this->m_main->StopRenderLoop(); + this->m_main.reset(); + } catch (std::exception &ex) { + Utils::Logf("StreamPage::Page_Unloaded m_main threw an exception: %s\n", ex.what()); } catch (...) { - Utils::Log("StreamPage::Page_Unloaded StopRenderLoop threw an exception\n"); + Utils::Log("StreamPage::Page_Unloaded m_main threw an exception\n"); } - this->m_main.reset(); Utils::Log("StreamPage::Page_Unloaded m_main reset\n"); } @@ -273,7 +253,7 @@ void StreamPage::disconnectAndCloseButton_Click(Platform::Object ^ sender, Windo // UI is sent back to HostSelectorPage in StartRenderLoop(), after the loop exits // All we need to do is close the progress dialog - DISPATCH_UI([progressToken], { + DISPATCH_UI([progressToken] { ::moonlight_xbox_dx::ModalDialog::HideDialogByToken(progressToken); }); } catch (...) { diff --git a/Pages/StreamPage.xaml.h b/Pages/StreamPage.xaml.h index dbb1957a..45920c70 100644 --- a/Pages/StreamPage.xaml.h +++ b/Pages/StreamPage.xaml.h @@ -99,6 +99,12 @@ namespace moonlight_xbox_dx } } + property TextBlock^ m_stepText { + TextBlock^ get() { + return this->StepText; + } + } + property StackPanel^ m_keyboardView { StackPanel^ get() { return this->KeyboardView; diff --git a/State/MoonlightClient.cpp b/State/MoonlightClient.cpp index 8cc92a26..6811ad83 100644 --- a/State/MoonlightClient.cpp +++ b/State/MoonlightClient.cpp @@ -289,9 +289,17 @@ int MoonlightClient::StartStreaming(std::shared_ptr res, St char message[2048]; sprintf(message, "gs_startapp failed with status code %d\n", a); Utils::Log(message); - Utils::Log(gs_error); + + if (gs_error) { + char errorMessage[2048]; + sprintf(errorMessage, "%s\n", gs_error); + + Utils::Log(errorMessage); + this->OnFailed(0, a, errorMessage); + } return a; } + // Sleep(10000); connectedInstance = this; CONNECTION_LISTENER_CALLBACKS callbacks; @@ -311,10 +319,17 @@ int MoonlightClient::StartStreaming(std::shared_ptr res, St DECODER_RENDERER_CALLBACKS rCallbacks = FFMpegDecoder::getDecoder(); AUDIO_RENDERER_CALLBACKS aCallbacks = AudioPlayer::getDecoder(); + int k = LiStartConnection(&serverData.serverInfo, &config, &callbacks, &rCallbacks, &aCallbacks, NULL, 0, NULL, 0); + sprintf(message, "LiStartConnection %d\n", k); Utils::Log(message); - return k; + + if (k != 0) { + this->OnFailed(0, k, "Connection failed"); + } + + return k; } void MoonlightClient::StopStreaming() { @@ -326,6 +341,16 @@ void log_message(const char *fmt, ...) { va_start(argp, fmt); char message[2048]; vsprintf_s(message, fmt, argp); + + // Append a single '\n' only if the string doesn't already end with one. + size_t len = strlen(message); + if (len == 0 || message[len - 1] != '\n') { + if (len + 1 < sizeof(message)) { + message[len] = '\n'; + message[len + 1] = '\0'; + } + } + Utils::Log(message); } @@ -340,13 +365,14 @@ void connection_started() { void connection_status_update(int status) { char message[4096]; - sprintf(message, "Stage %d started\n", status); + auto stageName = LiGetFormattedStageName(status); + sprintf(message, "Stage %d: '%s' - Started\n", status, LiGetFormattedStageName(status)); Utils::Log(message); } void connection_status_completed(int status) { char message[4096]; - sprintf(message, "Stage %d completed\n", status); + sprintf(message, "Stage %d: '%s' - Completed\n", status, LiGetFormattedStageName(status)); Utils::Log(message); if (connectedInstance->OnStatusUpdate != nullptr) { connectedInstance->OnStatusUpdate(status); @@ -370,10 +396,10 @@ void connection_terminated(int status) { void stage_failed(int stage, int err) { char message[4096]; unsigned int portFlags = LiGetPortFlagsFromStage(stage); - int portResult = LiTestClientConnectivity("qt.conntest.moonlight-stream.org", 443, portFlags); + // int portResult = LiTestClientConnectivity("qt.conntest.moonlight-stream.org", 443, portFlags); char failingPorts[128]; LiStringifyPortFlags(portFlags, ", ", failingPorts, sizeof(failingPorts)); - sprintf(message, "%s failed with error %d.\n Check Firewall and Connections to port: %s\n", LiGetStageName(stage), err, failingPorts); + sprintf(message, "Stage %d: '%s' - Failed with error: %d.\n", stage, LiGetFormattedStageName(stage), err, failingPorts); Utils::Log(message); if (connectedInstance->OnFailed != nullptr) { connectedInstance->OnFailed(stage, err, message); diff --git a/State/StreamConfiguration.h b/State/StreamConfiguration.h index d553df35..f0baa3c6 100644 --- a/State/StreamConfiguration.h +++ b/State/StreamConfiguration.h @@ -6,6 +6,7 @@ namespace moonlight_xbox_dx public: property Platform::String^ hostname; property int appID; + property Platform::String^ appName; property int width; property int height; property int bitrate; diff --git a/Streaming/VideoRenderer.cpp b/Streaming/VideoRenderer.cpp index 2ca887dd..1f6c1e8d 100644 --- a/Streaming/VideoRenderer.cpp +++ b/Streaming/VideoRenderer.cpp @@ -10,6 +10,7 @@ #include extern "C" { +#include "libgamestream/errors.h" #include "libgamestream/client.h" #include #include @@ -50,11 +51,11 @@ typedef struct _CSC_CONST_BUF } CSC_CONST_BUF, * PCSC_CONST_BUF; static_assert(sizeof(CSC_CONST_BUF) % 16 == 0, "Constant buffer sizes must be a multiple of 16"); - // Loads vertex and pixel shaders from files and instantiates the cube geometry. VideoRenderer::VideoRenderer(const std::shared_ptr& deviceResources, MoonlightClient* mclient, StreamConfiguration^ sConfig) : m_LastColorTrc(AVCOL_TRC_UNSPECIFIED), m_loadingComplete(false), + m_loadingSuccessful(false), m_deviceResources(deviceResources), client(mclient), configuration(sConfig) @@ -94,7 +95,7 @@ bool renderedOneFrame = false; // Renders one frame using the vertex and pixel shaders. bool VideoRenderer::Render(AVFrame *frame) { // Loading is asynchronous. Only draw geometry after it's loaded. - if (!m_loadingComplete.load(std::memory_order_acquire)) { + if (!m_loadingComplete.load(std::memory_order_acquire) && !m_loadingSuccessful.load(std::memory_order_acquire)) { return true; } @@ -192,26 +193,26 @@ void VideoRenderer::CreateDeviceDependentResources() { auto vertexShaderBytecode = DX::ReadData(L"Assets\\Shader\\d3d11_vertex.fxc"); DX::ThrowIfFailed( - m_deviceResources->GetD3DDevice()->CreateVertexShader( - vertexShaderBytecode.data(), - vertexShaderBytecode.size(), - nullptr, + m_deviceResources->GetD3DDevice()->CreateVertexShader( + vertexShaderBytecode.data(), + vertexShaderBytecode.size(), + nullptr, &m_vertexShader ) , "Vertex Shader Creation"); static const D3D11_INPUT_ELEMENT_DESC vertexDesc[] = - { + { { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 8, D3D11_INPUT_PER_VERTEX_DATA, 0 }, - }; + }; DX::ThrowIfFailed( - m_deviceResources->GetD3DDevice()->CreateInputLayout( - vertexDesc, - ARRAYSIZE(vertexDesc), - vertexShaderBytecode.data(), - vertexShaderBytecode.size(), + m_deviceResources->GetD3DDevice()->CreateInputLayout( + vertexDesc, + ARRAYSIZE(vertexDesc), + vertexShaderBytecode.data(), + vertexShaderBytecode.size(), &m_inputLayout ) , "Input Layout Creation"); @@ -221,10 +222,10 @@ void VideoRenderer::CreateDeviceDependentResources() { auto pixelShaderBytecode = DX::ReadData(L"Assets\\Shader\\d3d11_yuv420_pixel.fxc"); DX::ThrowIfFailed( - m_deviceResources->GetD3DDevice()->CreatePixelShader( - pixelShaderBytecode.data(), - pixelShaderBytecode.size(), - nullptr, + m_deviceResources->GetD3DDevice()->CreatePixelShader( + pixelShaderBytecode.data(), + pixelShaderBytecode.size(), + nullptr, &m_pixelShaderYUV420 ) , "Pixel Shader Creation"); @@ -277,42 +278,27 @@ void VideoRenderer::CreateDeviceDependentResources() DX::ThrowIfFailed(m_deviceResources->GetD3DDevice()->CreateBuffer(&indexBufferDesc, &indexBufferData, &m_indexBuffer), "Index Buffer creation"); } - int width = configuration->width; - int height = configuration->height; - - int status = client->StartStreaming(m_deviceResources, configuration); - if (status != 0) { - Windows::UI::Xaml::Controls::ContentDialog^ dialog = ref new Windows::UI::Xaml::Controls::ContentDialog(); - Utils::logMutex.lock(); - std::wstring m_text = L""; - std::vector lines = Utils::GetLogLines(); - for (int i = 0; i < lines.size(); i++) { - //Get only the last 24 lines - if (lines.size() - i < 24) { - m_text += lines[i]; - } + DISPATCH_THREADPOOL(([this, devRes = m_deviceResources, cfg = configuration] { + int status = this->client->StartStreaming(devRes, cfg); + + if (status != 0) { + Utils::Logf("StartStreaming failed with status %d\n", status); + m_loadingSuccessful.store(false, std::memory_order_release); + m_loadingComplete.store(true, std::memory_order_release); + return; } - Utils::logMutex.unlock(); - Utils::showLogs = true; - auto sv = ref new Windows::UI::Xaml::Controls::ScrollViewer(); - sv->VerticalScrollMode = Windows::UI::Xaml::Controls::ScrollMode::Enabled; - sv->VerticalScrollBarVisibility = Windows::UI::Xaml::Controls::ScrollBarVisibility::Visible; - auto tb = ref new Windows::UI::Xaml::Controls::TextBlock(); - tb->Text = ref new Platform::String(m_text.c_str()); - sv->Content = tb; - dialog->Content = sv; - dialog->CloseButtonText = L"OK"; - dialog->ShowAsync(); - return; - } - m_loadingComplete.store(true, std::memory_order_release); - Utils::Log("Loading Complete!\n"); + + m_loadingSuccessful.store(true, std::memory_order_release); + m_loadingComplete.store(true, std::memory_order_release); + Utils::Log("Loading Complete!\n"); + })); } void VideoRenderer::ReleaseDeviceDependentResources() { Windows::Graphics::Display::Core::HdmiDisplayInformation^ hdi = Windows::Graphics::Display::Core::HdmiDisplayInformation::GetForCurrentView(); m_loadingComplete.store(false, std::memory_order_release); + m_loadingSuccessful.store(false, std::memory_order_release); m_vertexShader.Reset(); m_inputLayout.Reset(); m_pixelShaderYUV420.Reset(); diff --git a/Streaming/VideoRenderer.h b/Streaming/VideoRenderer.h index 21118983..48176378 100644 --- a/Streaming/VideoRenderer.h +++ b/Streaming/VideoRenderer.h @@ -4,6 +4,7 @@ #include "Common\StepTimer.h" #include "State\MoonlightClient.h" #include "State\StreamConfiguration.h" +#include extern "C" { #include @@ -32,6 +33,8 @@ namespace moonlight_xbox_dx class VideoRenderer { public: + bool IsLoadingComplete() const { return m_loadingComplete.load(std::memory_order_acquire); } + bool IsLoadingSuccessful() const { return m_loadingSuccessful.load(std::memory_order_acquire); } VideoRenderer(const std::shared_ptr& deviceResources,MoonlightClient *client,StreamConfiguration ^sConfig); void CreateDeviceDependentResources(); void CreateWindowSizeDependentResources(); @@ -73,6 +76,7 @@ namespace moonlight_xbox_dx // Variables used with the rendering loop. DXGI_HDR_METADATA_HDR10 m_lastHdr10; std::atomic m_loadingComplete; + std::atomic m_loadingSuccessful; MoonlightClient *client; StreamConfiguration^ configuration; diff --git a/Streaming/moonlight_xbox_dxMain.cpp b/Streaming/moonlight_xbox_dxMain.cpp index 67bc6dca..b3422e21 100644 --- a/Streaming/moonlight_xbox_dxMain.cpp +++ b/Streaming/moonlight_xbox_dxMain.cpp @@ -4,6 +4,8 @@ #include "../Plot/ImGuiPlots.h" #include "Utils.hpp" #include +#include +#include #include using namespace Windows::Gaming::Input; @@ -17,12 +19,90 @@ using namespace Windows::UI::ViewManagement::Core; extern "C" { #include +#include } // Loads and initializes application assets when the application is loaded. moonlight_xbox_dxMain::moonlight_xbox_dxMain(const std::shared_ptr& deviceResources, StreamPage^ streamPage, MoonlightClient* client, StreamConfiguration^ configuration) : - m_deviceResources(deviceResources), m_pointerLocationX(0.0f), m_streamPage(streamPage), moonlightClient(client) { + + Platform::String ^ appName = configuration->appName ? "'" + configuration->appName + "'" : "App"; + DISPATCH_UI(([streamPage, appName]() { + streamPage->m_stepText->Text = "Starting " + appName; + })); + + client->OnFailed = ([this, appName](int status, int error, char *message) { + std::string msgCopy = message ? message : std::string(); + auto self = this; + + DISPATCH_UI(([msgCopy, self, appName]() { + auto showErrorDialog = std::make_shared>(); + auto showLogsDialog = std::make_shared>(); + + *showErrorDialog = [self, msgCopy, showLogsDialog, appName]() { + auto dialog1 = ref new Windows::UI::Xaml::Controls::ContentDialog(); + dialog1->Title = L"Failed to start " + appName; + dialog1->Content = Utils::StringFromStdString(msgCopy); + dialog1->PrimaryButtonText = L"OK"; + dialog1->SecondaryButtonText = L"Show Logs"; + + concurrency::create_task(::moonlight_xbox_dx::ModalDialog::ShowOnceAsync(dialog1)).then([self, showLogsDialog](concurrency::task t) { + auto result = t.get(); + if (result == Windows::UI::Xaml::Controls::ContentDialogResult::Primary) { + self->StopRenderLoop(); + self->ExitStreamPage(); + } + else if (result == Windows::UI::Xaml::Controls::ContentDialogResult::Secondary) { + (*showLogsDialog)(); + } + }); + }; + + *showLogsDialog = [self, showErrorDialog]() { + auto dialog2 = ref new Windows::UI::Xaml::Controls::ContentDialog(); + + std::wstring m_text = L""; + std::vector lines = Utils::GetLogLines(); + + for (int i = 0; i < (int)lines.size(); i++) { + // Get only the last 8 lines + // More than that cannot be fully viewed on the screen at the current scaling + if ((int)lines.size() - i <= 8) { + m_text += lines[i]; + } + } + + Utils::showLogs = true; + + dialog2->MaxWidth = 600; + dialog2->Title = "Logs"; + dialog2->Content = ref new Platform::String(m_text.c_str()); + dialog2->PrimaryButtonText = L"OK"; + dialog2->SecondaryButtonText = L"Show Error"; + + concurrency::create_task(::moonlight_xbox_dx::ModalDialog::ShowOnceAsync(dialog2)).then([self, showErrorDialog](concurrency::task t) { + auto result = t.get(); + if (result == Windows::UI::Xaml::Controls::ContentDialogResult::Primary) { + self->StopRenderLoop(); + self->ExitStreamPage(); + } + else if (result == Windows::UI::Xaml::Controls::ContentDialogResult::Secondary) { + (*showErrorDialog)(); + } + }); + }; + + // Start by showing the error dialog + (*showErrorDialog)(); + })); + }); + + client->OnStatusUpdate = ([streamPage](int status) { + std::string msg = LiGetFormattedStageName(status); + DISPATCH_UI(([streamPage, msg]() { + streamPage->m_stepText->Text = Utils::StringFromStdString(msg); + })); + }); // Register to be notified if the Device is lost or recreated m_deviceResources->RegisterDeviceNotify(this); @@ -32,6 +112,34 @@ moonlight_xbox_dxMain::moonlight_xbox_dxMain(const std::shared_ptrSetStats(m_stats); m_sceneRenderer = std::make_shared(m_deviceResources, moonlightClient, configuration); + + client->OnCompleted = ([this, streamPage, configuration]() { + concurrency::create_task([this]() { + while (this->m_sceneRenderer && !this->m_sceneRenderer->IsLoadingComplete() && !this->moonlightClient->IsConnectionTerminated()) { + Sleep(50); + } + }).then([this, streamPage, configuration](concurrency::task t) { + + if (this->m_sceneRenderer && this->m_sceneRenderer->IsLoadingSuccessful()) { + DISPATCH_UI(([streamPage]() { + Sleep(500); + streamPage->m_progressRing->IsActive = false; + streamPage->m_progressView->Visibility = Windows::UI::Xaml::Visibility::Collapsed; + })); + } + + }); + }); + + client->SetHDR = ([this](bool v) { + concurrency::create_task([this]() { + while (this->m_sceneRenderer && !this->m_sceneRenderer->IsLoadingComplete() && !this->moonlightClient->IsConnectionTerminated()) { + Sleep(50); + } + }).then([this, v](concurrency::task t) { + this->m_sceneRenderer->SetHDR(v); + }); + }); m_LogRenderer = std::make_unique(m_deviceResources); @@ -47,26 +155,6 @@ moonlight_xbox_dxMain::moonlight_xbox_dxMain(const std::shared_ptrSetShowImGui(configuration->enableGraphs); ImGuiPlots::instance().setEnabled(configuration->enableGraphs); - client->OnStatusUpdate = ([streamPage](int status) { - const char* msg = LiGetStageName(status); - streamPage->m_statusText->Text = Utils::StringFromStdString(std::string(msg)); - }); - - client->OnCompleted = ([streamPage]() { - // Give stream a moment to stabilize before hiding progress view - Sleep(500); - streamPage->m_progressRing->IsActive = false; - streamPage->m_progressView->Visibility = Windows::UI::Xaml::Visibility::Collapsed; - }); - - client->OnFailed = ([streamPage](int status, int error, char* message) { - streamPage->m_statusText->Text = Utils::StringFromStdString(std::string(message)); - }); - - client->SetHDR = ([this](bool v) { - this->m_sceneRenderer->SetHDR(v); - }); - m_timer.SetFixedTimeStep(false); double refreshRate = m_deviceResources->GetUWPRefreshRate(); @@ -203,12 +291,8 @@ void moonlight_xbox_dxMain::StartRenderLoop() StopRenderLoop(); // also stops input Disconnect(); - // Navigate back to home - DISPATCH_UI([], { - auto rootFrame = dynamic_cast(Windows::UI::Xaml::Window::Current->Content); - if (rootFrame) { - rootFrame->Navigate(Windows::UI::Xaml::Interop::TypeName(HostSelectorPage::typeid)); - } + DISPATCH_UI([this]() { + ExitStreamPage(); }); }); m_renderLoopWorker = ThreadPool::RunAsync(workItemHandler, WorkItemPriority::High, WorkItemOptions::TimeSliced); @@ -288,7 +372,7 @@ void moonlight_xbox_dxMain::ProcessInput() } } if (isCurrentlyPressed) { - DISPATCH_UI([this], { + DISPATCH_UI([this] { Windows::UI::Xaml::Controls::Flyout::ShowAttachedFlyout(m_streamPage->m_flyoutButton); }); @@ -542,6 +626,41 @@ void moonlight_xbox_dxMain::CloseApp() { moonlightClient->StopApp(); } +void moonlight_xbox_dxMain::ExitStreamPage() { + + bool reachedAppPage = false; + + try { + auto rootFrame = dynamic_cast(Windows::UI::Xaml::Window::Current->Content); + if (!rootFrame) return; + + auto current = dynamic_cast(rootFrame->Content); + if (current != nullptr) { + reachedAppPage = true; + } + + try { + rootFrame->GoBack(); + } catch (...) { + Utils::Log("ExitStreamPage: Failed to GoBack()\n"); + } + + if (!reachedAppPage) { + if (dynamic_cast(rootFrame->Content) != nullptr) reachedAppPage = true; + } + + if (!reachedAppPage) { + try { + rootFrame->Navigate(Windows::UI::Xaml::Interop::TypeName(HostSelectorPage::typeid)); + } catch (...) { + rootFrame->Content = nullptr; + Utils::Log("ExitStreamPage: Failed to return to HostSelectorPage\n"); + } + } + } catch (...) { + Utils::Log("ExitStreamPage: An error occurred\n"); + } +} void moonlight_xbox_dxMain::OnKeyDown(unsigned short virtualKey, char modifiers) { @@ -580,7 +699,7 @@ void moonlight_xbox_dxMain::SendWinAltB() { bool moonlight_xbox_dxMain::ToggleLogs() { bool visible = m_LogRenderer->GetVisible(); - DISPATCH_UI([&], { + DISPATCH_UI([&] { m_LogRenderer->ToggleVisible(); }); @@ -590,7 +709,7 @@ bool moonlight_xbox_dxMain::ToggleLogs() { bool moonlight_xbox_dxMain::ToggleStats() { bool visible = m_statsTextRenderer->GetVisible(); - DISPATCH_UI([&], { + DISPATCH_UI([&] { m_statsTextRenderer->ToggleVisible(); }); diff --git a/Streaming/moonlight_xbox_dxMain.h b/Streaming/moonlight_xbox_dxMain.h index b133152b..dc901abc 100644 --- a/Streaming/moonlight_xbox_dxMain.h +++ b/Streaming/moonlight_xbox_dxMain.h @@ -29,6 +29,7 @@ namespace moonlight_xbox_dx virtual void OnDeviceRestored(); void Disconnect(); void CloseApp(); + void ExitStreamPage(); void SendGuideButton(int duration); void SendWinAltB(); bool ToggleLogs(); diff --git a/moonlight-xbox-dx.sln b/moonlight-xbox-dx.sln index 3892e90a..54008c99 100644 --- a/moonlight-xbox-dx.sln +++ b/moonlight-xbox-dx.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31912.275 +# Visual Studio Version 18 +VisualStudioVersion = 18.2.11408.102 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "moonlight-xbox-dx", "moonlight-xbox-dx.vcxproj", "{0D5DFFAA-E5DD-4A61-B3EC-0A73EF86C9B1}" ProjectSection(ProjectDependencies) = postProject @@ -11,11 +11,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "moonlight-xbox-dx", "moonli {F4776924-619C-42C7-88B2-82C947CCC9E7} = {F4776924-619C-42C7-88B2-82C947CCC9E7} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "moonlight-common-c", "third_party\moonlight-common-c\build\moonlight-common-c.vcxproj", "{E025A414-0DA7-3C61-A890-9487ED6E1D91}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "moonlight-common-c", "third_party\moonlight-common-c\build\moonlight-common-c.vcxproj", "{56F17673-A685-3B16-9094-8B11198EF0BC}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gamestream", "libgamestream\build\gamestream.vcxproj", "{873D03B2-B99F-3917-AFDC-9FB954F9962E}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gamestream", "libgamestream\build\gamestream.vcxproj", "{B089477D-7A5A-3EF1-8139-BC31842134E6}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "enet", "third_party\moonlight-common-c\build\enet\enet.vcxproj", "{842AAF8B-4505-3D3B-AAE5-CFF297F90298}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "enet", "third_party\moonlight-common-c\build\enet\enet.vcxproj", "{2CB3B76D-BADC-3BF6-90C9-9D52F4B53A93}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DirectXTK", "third_party\DirectXTK\DirectXTK_Windows10_2022.vcxproj", "{F4776924-619C-42C7-88B2-82C947CCC9E7}" EndProject @@ -39,30 +39,30 @@ Global {0D5DFFAA-E5DD-4A61-B3EC-0A73EF86C9B1}.RelWithDebInfo|x64.ActiveCfg = Release|x64 {0D5DFFAA-E5DD-4A61-B3EC-0A73EF86C9B1}.RelWithDebInfo|x64.Build.0 = Release|x64 {0D5DFFAA-E5DD-4A61-B3EC-0A73EF86C9B1}.RelWithDebInfo|x64.Deploy.0 = Release|x64 - {E025A414-0DA7-3C61-A890-9487ED6E1D91}.Debug|x64.ActiveCfg = Debug|x64 - {E025A414-0DA7-3C61-A890-9487ED6E1D91}.Debug|x64.Build.0 = Debug|x64 - {E025A414-0DA7-3C61-A890-9487ED6E1D91}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {E025A414-0DA7-3C61-A890-9487ED6E1D91}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 - {E025A414-0DA7-3C61-A890-9487ED6E1D91}.Release|x64.ActiveCfg = Release|x64 - {E025A414-0DA7-3C61-A890-9487ED6E1D91}.Release|x64.Build.0 = Release|x64 - {E025A414-0DA7-3C61-A890-9487ED6E1D91}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {E025A414-0DA7-3C61-A890-9487ED6E1D91}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 - {873D03B2-B99F-3917-AFDC-9FB954F9962E}.Debug|x64.ActiveCfg = Debug|x64 - {873D03B2-B99F-3917-AFDC-9FB954F9962E}.Debug|x64.Build.0 = Debug|x64 - {873D03B2-B99F-3917-AFDC-9FB954F9962E}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {873D03B2-B99F-3917-AFDC-9FB954F9962E}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 - {873D03B2-B99F-3917-AFDC-9FB954F9962E}.Release|x64.ActiveCfg = Release|x64 - {873D03B2-B99F-3917-AFDC-9FB954F9962E}.Release|x64.Build.0 = Release|x64 - {873D03B2-B99F-3917-AFDC-9FB954F9962E}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {873D03B2-B99F-3917-AFDC-9FB954F9962E}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 - {842AAF8B-4505-3D3B-AAE5-CFF297F90298}.Debug|x64.ActiveCfg = Debug|x64 - {842AAF8B-4505-3D3B-AAE5-CFF297F90298}.Debug|x64.Build.0 = Debug|x64 - {842AAF8B-4505-3D3B-AAE5-CFF297F90298}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {842AAF8B-4505-3D3B-AAE5-CFF297F90298}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 - {842AAF8B-4505-3D3B-AAE5-CFF297F90298}.Release|x64.ActiveCfg = Release|x64 - {842AAF8B-4505-3D3B-AAE5-CFF297F90298}.Release|x64.Build.0 = Release|x64 - {842AAF8B-4505-3D3B-AAE5-CFF297F90298}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {842AAF8B-4505-3D3B-AAE5-CFF297F90298}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 + {56F17673-A685-3B16-9094-8B11198EF0BC}.Debug|x64.ActiveCfg = Debug|x64 + {56F17673-A685-3B16-9094-8B11198EF0BC}.Debug|x64.Build.0 = Debug|x64 + {56F17673-A685-3B16-9094-8B11198EF0BC}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 + {56F17673-A685-3B16-9094-8B11198EF0BC}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 + {56F17673-A685-3B16-9094-8B11198EF0BC}.Release|x64.ActiveCfg = Release|x64 + {56F17673-A685-3B16-9094-8B11198EF0BC}.Release|x64.Build.0 = Release|x64 + {56F17673-A685-3B16-9094-8B11198EF0BC}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 + {56F17673-A685-3B16-9094-8B11198EF0BC}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 + {B089477D-7A5A-3EF1-8139-BC31842134E6}.Debug|x64.ActiveCfg = Debug|x64 + {B089477D-7A5A-3EF1-8139-BC31842134E6}.Debug|x64.Build.0 = Debug|x64 + {B089477D-7A5A-3EF1-8139-BC31842134E6}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 + {B089477D-7A5A-3EF1-8139-BC31842134E6}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 + {B089477D-7A5A-3EF1-8139-BC31842134E6}.Release|x64.ActiveCfg = Release|x64 + {B089477D-7A5A-3EF1-8139-BC31842134E6}.Release|x64.Build.0 = Release|x64 + {B089477D-7A5A-3EF1-8139-BC31842134E6}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 + {B089477D-7A5A-3EF1-8139-BC31842134E6}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 + {2CB3B76D-BADC-3BF6-90C9-9D52F4B53A93}.Debug|x64.ActiveCfg = Debug|x64 + {2CB3B76D-BADC-3BF6-90C9-9D52F4B53A93}.Debug|x64.Build.0 = Debug|x64 + {2CB3B76D-BADC-3BF6-90C9-9D52F4B53A93}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 + {2CB3B76D-BADC-3BF6-90C9-9D52F4B53A93}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 + {2CB3B76D-BADC-3BF6-90C9-9D52F4B53A93}.Release|x64.ActiveCfg = Release|x64 + {2CB3B76D-BADC-3BF6-90C9-9D52F4B53A93}.Release|x64.Build.0 = Release|x64 + {2CB3B76D-BADC-3BF6-90C9-9D52F4B53A93}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 + {2CB3B76D-BADC-3BF6-90C9-9D52F4B53A93}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 {F4776924-619C-42C7-88B2-82C947CCC9E7}.Debug|x64.ActiveCfg = Debug|x64 {F4776924-619C-42C7-88B2-82C947CCC9E7}.Debug|x64.Build.0 = Debug|x64 {F4776924-619C-42C7-88B2-82C947CCC9E7}.MinSizeRel|x64.ActiveCfg = Release|x64 diff --git a/pch.h b/pch.h index a36b87b4..fb755620 100644 --- a/pch.h +++ b/pch.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once // Use the C++ standard templated min/max #define NOMINMAX @@ -19,8 +19,11 @@ #include #include #include +#include +#include #include #include +#include #include #include #include "App.xaml.h" @@ -35,14 +38,20 @@ #endif // Helper for dispatching code to the UI thread -#define DISPATCH_UI(CAPTURE, CODE) \ +#define DISPATCH_UI(LAMBDA) \ Windows::ApplicationModel::Core::CoreApplication::MainView->CoreWindow \ ->Dispatcher \ ->RunAsync( \ - Windows::UI::Core::CoreDispatcherPriority::High, \ - ref new Windows::UI::Core::DispatchedHandler(CAPTURE CODE) \ + Windows::UI::Core::CoreDispatcherPriority::Normal, \ + ref new Windows::UI::Core::DispatchedHandler(LAMBDA) \ ) +// Helper for dispatching code to the threadpool +#define DISPATCH_THREADPOOL(LAMBDA) \ + Windows::System::Threading::ThreadPool::RunAsync( \ + ref new Windows::System::Threading::WorkItemHandler([=](Windows::Foundation::IAsyncAction^) { LAMBDA(); }) \ + ) + // Time helpers static inline int64_t QpcFreq() { static int64_t f = [] { @@ -225,3 +234,21 @@ static inline void SleepUntilQpc(int64_t targetQpc, int64_t sleepSlackUs = 1000) YieldProcessor(); } } + +static inline const char *LiGetFormattedStageName(int status) { + const char *stageName = ::LiGetStageName(status); + if (!stageName) { + return nullptr; + } + + thread_local std::string s; + s = stageName; + + if (!s.empty()) { + s[0] = static_cast(std::toupper(static_cast(s[0]))); + } + + return s.c_str(); +} + +