Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified Common/ModalDialog.xaml.cpp
Binary file not shown.
2 changes: 1 addition & 1 deletion Package.appxmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@
<Capability Name="privateNetworkClientServer"/>
<rescap:Capability Name="hevcPlayback" />
</Capabilities>
</Package>
</Package>
22 changes: 21 additions & 1 deletion Pages/AppPage.xaml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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();
Expand All @@ -34,6 +50,7 @@ AppPage::AppPage()
}

void AppPage::OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) {

MoonlightHost^ mhost = dynamic_cast<MoonlightHost^>(e->Parameter);
if (mhost == nullptr) return;
host = mhost;
Expand Down Expand Up @@ -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;
Expand All @@ -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");
}
Expand Down
9 changes: 1 addition & 8 deletions Pages/HostSelectorPage.xaml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {

@mpotrykus mpotrykus Feb 11, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Snuck this in. Removed the WoL Success dialog. This just feels silly to have now that we have a visual polling feedback.

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.";
Expand Down
3 changes: 2 additions & 1 deletion Pages/StreamPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@
<Grid x:Name="ProgressView" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<StackPanel x:Name="ProgressStack" HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Vertical">
<muxc:ProgressRing x:Name="MainProgressRing" Width="64" Height="64" IsActive="True" Margin="0,0,0,16" />
<TextBlock x:Name="StatusText" Text="Initializing Moonlight..."/>
<TextBlock x:Name="StatusText" TextAlignment="Center" Text="Initializing Moonlight..."/>
<TextBlock x:Name="StepText" FontSize="12px" Foreground="DimGray" TextAlignment="Center" Text="" Margin="0,16,0,0" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

UWP XAML FontSize "px" suffix support documentation

💡 Result:

UWP XAML does not support a "px" suffix for FontSize (or other double-typed size properties). In UWP, FontSize is a double value expressed in pixels (effective/device-independent pixels in practice), and XAML attribute values for primitives are parsed from plain numeric strings (so "12" works; "12px" won’t). [1][2]

Use:

<TextBlock FontSize="12" />

Related documentation:

  • UWP/WinUI API docs show FontSize as double and “measured in pixels” (example: FontIconSource.FontSize). [1]
  • UWP XAML syntax docs describe primitive properties (like double) being set via attribute syntax using the built-in string-to-primitive conversion (no unit suffixes described). [2]

Sources

  1. Microsoft Learn — FontIconSource.FontSize (UWP): double, measured in pixels. (learn.microsoft.com)
  2. Microsoft Learn — UWP “XAML syntax guide” (primitive types like double use built-in parser conversion from strings). (learn.microsoft.com)

Citations:


FontSize="12px" — UWP XAML does not support the px suffix; use FontSize="12" instead.

In UWP XAML, FontSize expects a bare numeric value (device-independent pixels). The "12px" string will cause a XAML parse error at runtime.

Proposed fix
-                <TextBlock x:Name="StepText" FontSize="12px" Foreground="DimGray" TextAlignment="Center" Text="" Margin="0,16,0,0" />
+                <TextBlock x:Name="StepText" FontSize="12" Foreground="DimGray" TextAlignment="Center" Text="" Margin="0,16,0,0" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<TextBlock x:Name="StepText" FontSize="12px" Foreground="DimGray" TextAlignment="Center" Text="" Margin="0,16,0,0" />
<TextBlock x:Name="StepText" FontSize="12" Foreground="DimGray" TextAlignment="Center" Text="" Margin="0,16,0,0" />
🤖 Prompt for AI Agents
In `@Pages/StreamPage.xaml` at line 77, The TextBlock named StepText uses an
invalid FontSize string ("12px"); change the FontSize attribute on StepText to a
numeric value without the "px" suffix (e.g., FontSize="12") so UWP XAML parses
correctly and uses device-independent pixels.

</StackPanel>
</Grid>
</Grid>
Expand Down
64 changes: 22 additions & 42 deletions Pages/StreamPage.xaml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<StreamPage>();
if (that == nullptr) return;
try {
that->m_main = std::unique_ptr<moonlight_xbox_dxMain>(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");
}
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

void StreamPage::Page_Unloaded(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e) {
Expand All @@ -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");
Comment on lines 106 to 115

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

m_main.reset() is skipped if StopRenderLoop() throws — resource leak.

If StopRenderLoop() throws, execution jumps to the catch block and m_main.reset() on line 108 is never reached, yet line 115 still logs "m_main reset". Move the reset into a finally-style pattern (or duplicate it in the catch blocks) so the resource is always released.

Proposed fix
 	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 m_main threw an exception\n");
 	}
+
+	this->m_main.reset();
 
 	Utils::Log("StreamPage::Page_Unloaded m_main reset\n");
🤖 Prompt for AI Agents
In `@Pages/StreamPage.xaml.cpp` around lines 106 - 115, The try/catch allows
StopRenderLoop() to throw and skips m_main.reset(), leaking the resource and
leaving the "m_main reset" log incorrect; refactor Page_Unloaded so
m_main.reset() always runs regardless of StopRenderLoop() outcome (e.g., call
StopRenderLoop() in its try/catch but move m_main.reset() into a single
post-catch cleanup section or call m_main.reset() in each catch), preserve
logging of exceptions using the existing catch blocks (Utils::Logf/Utils::Log)
and only log "m_main reset" after m_main.reset() actually executes; update
references in the method using StopRenderLoop, m_main.reset and the catch
handlers accordingly.

}

Expand Down Expand Up @@ -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 (...) {
Expand Down
6 changes: 6 additions & 0 deletions Pages/StreamPage.xaml.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
38 changes: 32 additions & 6 deletions State/MoonlightClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,9 +289,17 @@ int MoonlightClient::StartStreaming(std::shared_ptr<DX::DeviceResources> 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;
Expand All @@ -311,10 +319,17 @@ int MoonlightClient::StartStreaming(std::shared_ptr<DX::DeviceResources> 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() {
Expand All @@ -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);
}

Expand All @@ -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);
}
Comment on lines 366 to 371

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Same nullptr risk in connection_status_update; also stageName on line 368 is unused.

LiGetFormattedStageName(status) can return nullptr. Line 368 captures it but line 369 calls the function again and passes the result directly to sprintf %s.

Proposed fix
-	auto stageName = LiGetFormattedStageName(status);
-	sprintf(message, "Stage %d: '%s' - Started\n", status, LiGetFormattedStageName(status));
+	const char* stageName = LiGetFormattedStageName(status);
+	sprintf(message, "Stage %d: '%s' - Started\n", status, stageName ? stageName : "Unknown");
🤖 Prompt for AI Agents
In `@State/MoonlightClient.cpp` around lines 366 - 371, The function
connection_status_update calls LiGetFormattedStageName(status) twice and passes
its return directly into sprintf %s, which risks a nullptr dereference and
wastes the previously captured stageName; fix by reusing the captured stageName
variable, check if stageName is nullptr and substitute a safe placeholder (e.g.
"<unknown>") before calling sprintf/Utils::Log, and prefer a bounded/safer
formatting function or ensure the buffer size is respected when writing the
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);
Comment on lines 373 to 376

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Same nullptr risk in connection_status_completed.

Proposed fix
-	sprintf(message, "Stage %d: '%s' - Completed\n", status, LiGetFormattedStageName(status));
+	const char* stageName = LiGetFormattedStageName(status);
+	sprintf(message, "Stage %d: '%s' - Completed\n", status, stageName ? stageName : "Unknown");
🤖 Prompt for AI Agents
In `@State/MoonlightClient.cpp` around lines 373 - 376, The function
connection_status_completed calls LiGetFormattedStageName(status) and passes its
return directly into sprintf which risks dereferencing a nullptr (and possible
overflow); update connection_status_completed to first capture the return of
LiGetFormattedStageName into a local pointer, check it for nullptr and
substitute a safe fallback string (e.g. "<unknown>") if null, and use a bounded
formatting call (e.g. snprintf) or ensure the buffer size is respected before
calling Utils::Log so neither a null pointer nor buffer overflow can occur.

if (connectedInstance->OnStatusUpdate != nullptr) {
connectedInstance->OnStatusUpdate(status);
Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions State/StreamConfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading