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();
+}
+
+