aboutsummaryrefslogtreecommitdiffstats
path: root/support/hololens
diff options
context:
space:
mode:
authorPaul Rouget <me@paulrouget.com>2020-06-17 11:36:04 +0200
committerJosh Matthews <josh@joshmatthews.net>2020-06-17 23:22:22 -0400
commitbd8c7d6f4d0c84f901bb0822cb48deb38b86037f (patch)
treeda48d8fd646a2d0b111fd4d672d95bb1471545f7 /support/hololens
parent2560e78f11d197f40b3a44069cf68a0609d7081a (diff)
downloadservo-bd8c7d6f4d0c84f901bb0822cb48deb38b86037f.tar.gz
servo-bd8c7d6f4d0c84f901bb0822cb48deb38b86037f.zip
[UWP] Basic console panel
Diffstat (limited to 'support/hololens')
-rw-r--r--support/hololens/ServoApp/BrowserPage.cpp91
-rw-r--r--support/hololens/ServoApp/BrowserPage.h9
-rw-r--r--support/hololens/ServoApp/BrowserPage.xaml8
-rw-r--r--support/hololens/ServoApp/Devtools/Client.cpp237
-rw-r--r--support/hololens/ServoApp/Devtools/Client.h57
-rw-r--r--support/hololens/ServoApp/ServoApp.vcxproj4
-rw-r--r--support/hololens/ServoApp/ServoApp.vcxproj.filters9
-rw-r--r--support/hololens/ServoApp/Strings/en-US/Resources.resw3
-rw-r--r--support/hololens/ServoApp/pch.h15
9 files changed, 400 insertions, 33 deletions
diff --git a/support/hololens/ServoApp/BrowserPage.cpp b/support/hololens/ServoApp/BrowserPage.cpp
index e81578a48d6..85b1c0c5885 100644
--- a/support/hololens/ServoApp/BrowserPage.cpp
+++ b/support/hololens/ServoApp/BrowserPage.cpp
@@ -7,11 +7,7 @@
#include "BrowserPage.h"
#include "BrowserPage.g.cpp"
#include "DefaultUrl.h"
-
-#include "winrt/Microsoft.UI.Xaml.Controls.h"
-#include "winrt/Microsoft.UI.Xaml.XamlTypeInfo.h"
-#include "winrt/Windows.UI.Text.h"
-#include "winrt/Windows.UI.Xaml.Documents.h" // For Run.Text()
+#include "Devtools/Client.h"
using namespace std::placeholders;
using namespace winrt::Windows::Foundation;
@@ -21,7 +17,9 @@ using namespace winrt::Windows::UI::ViewManagement;
using namespace winrt::Windows::ApplicationModel::Core;
using namespace winrt::Windows::ApplicationModel::Resources;
using namespace winrt::Windows::UI::Notifications;
+using namespace winrt::Windows::Data::Json;
using namespace winrt::Windows::Data::Xml::Dom;
+using namespace winrt::servo;
namespace winrt::ServoApp::implementation {
@@ -46,6 +44,7 @@ void BrowserPage::BindServoEvents() {
reloadButton().Visibility(Visibility::Collapsed);
stopButton().IsEnabled(true);
stopButton().Visibility(Visibility::Visible);
+ devtoolsButton().IsEnabled(true);
});
servoControl().OnLoadEnded([=] {
urlbarLoadingIndicator().IsActive(false);
@@ -64,22 +63,20 @@ void BrowserPage::BindServoEvents() {
urlTextbox().GotFocus(std::bind(&BrowserPage::OnURLFocused, this, _1));
servoControl().OnMediaSessionMetadata(
[=](hstring title, hstring artist, hstring album) {});
- servoControl().OnMediaSessionPlaybackStateChange(
- [=](const auto &, int state) {
- if (state == servo::Servo::MediaSessionPlaybackState::None) {
- mediaControls().Visibility(Visibility::Collapsed);
- return;
- }
- mediaControls().Visibility(Visibility::Visible);
- playButton().Visibility(
- state == servo::Servo::MediaSessionPlaybackState::Paused
- ? Visibility::Visible
- : Visibility::Collapsed);
- pauseButton().Visibility(
- state == servo::Servo::MediaSessionPlaybackState::Paused
- ? Visibility::Collapsed
- : Visibility::Visible);
- });
+ servoControl().OnMediaSessionPlaybackStateChange([=](const auto &,
+ int state) {
+ if (state == Servo::MediaSessionPlaybackState::None) {
+ mediaControls().Visibility(Visibility::Collapsed);
+ return;
+ }
+ mediaControls().Visibility(Visibility::Visible);
+ playButton().Visibility(state == Servo::MediaSessionPlaybackState::Paused
+ ? Visibility::Visible
+ : Visibility::Collapsed);
+ pauseButton().Visibility(state == Servo::MediaSessionPlaybackState::Paused
+ ? Visibility::Collapsed
+ : Visibility::Visible);
+ });
servoControl().OnDevtoolsStatusChanged(
[=](DevtoolsStatus status, unsigned int port) {
mDevtoolsStatus = status;
@@ -276,11 +273,49 @@ void BrowserPage::OnPrefererenceSearchboxEdited(
}
}
+void BrowserPage::OnDevtoolsMessage(DevtoolsMessageLevel level, hstring source,
+ hstring body) {
+ Dispatcher().RunAsync(CoreDispatcherPriority::Low, [=] {
+ // Temporary text-based logs. Should use gridview.
+ auto paragraph = Documents::Paragraph();
+
+ auto run1 = Documents::Run();
+ if (level == DevtoolsMessageLevel::Warn) {
+ run1.Text(L"warn: ");
+ } else if (level == DevtoolsMessageLevel::Error) {
+ run1.Text(L"error: ");
+ } else if (level == DevtoolsMessageLevel::None) {
+ run1.Text(L"");
+ }
+ paragraph.Inlines().Append(run1);
+
+ auto run2 = Documents::Run();
+ run2.Text(body);
+ paragraph.Inlines().Append(run2);
+
+ auto run3 = Documents::Run();
+ run3.Text(L" " + source);
+ paragraph.Inlines().Append(run3);
+
+ DevtoolsConsoleOutput().Blocks().Append(paragraph);
+
+ // Scroll to last message
+ auto offset = DevtoolsConsoleScrollViewer().ExtentHeight();
+ DevtoolsConsoleScrollViewer().ChangeView(nullptr, offset, nullptr);
+ });
+}
+
+void BrowserPage::OnDevtoolsDetached() {}
+
void BrowserPage::OnDevtoolsButtonClicked(IInspectable const &,
RoutedEventArgs const &) {
if (toolbox().Visibility() == Visibility::Visible) {
prefList().Children().Clear();
toolbox().Visibility(Visibility::Collapsed);
+ DevtoolsConsoleOutput().Blocks().Clear();
+ if (mDevtoolsClient != nullptr) {
+ mDevtoolsClient->Stop();
+ }
return;
}
@@ -290,10 +325,16 @@ void BrowserPage::OnDevtoolsButtonClicked(IInspectable const &,
auto resourceLoader = ResourceLoader::GetForCurrentView();
if (mDevtoolsStatus == DevtoolsStatus::Running) {
+ hstring port = to_hstring(mDevtoolsPort);
+ if (mDevtoolsClient == nullptr) {
+ DevtoolsDelegate *dd = static_cast<DevtoolsDelegate *>(this);
+ mDevtoolsClient =
+ std::make_unique<DevtoolsClient>(L"localhost", port, *dd);
+ }
+ mDevtoolsClient->Run();
std::wstring message =
resourceLoader.GetString(L"devtoolsStatus/Running").c_str();
- std::wstring formatted =
- format(message, std::to_wstring(mDevtoolsPort).c_str());
+ std::wstring formatted = format(message, port.c_str());
DevtoolsStatusMessage().Text(formatted);
} else if (mDevtoolsStatus == DevtoolsStatus::Failed) {
DevtoolsStatusMessage().Text(
@@ -317,12 +358,12 @@ void BrowserPage::OnURLEdited(IInspectable const &,
void BrowserPage::OnMediaControlsPlayClicked(IInspectable const &,
RoutedEventArgs const &) {
servoControl().SendMediaSessionAction(
- static_cast<int32_t>(servo::Servo::MediaSessionActionType::Play));
+ static_cast<int32_t>(Servo::MediaSessionActionType::Play));
}
void BrowserPage::OnMediaControlsPauseClicked(IInspectable const &,
RoutedEventArgs const &) {
servoControl().SendMediaSessionAction(
- static_cast<int32_t>(servo::Servo::MediaSessionActionType::Pause));
+ static_cast<int32_t>(Servo::MediaSessionActionType::Pause));
}
} // namespace winrt::ServoApp::implementation
diff --git a/support/hololens/ServoApp/BrowserPage.h b/support/hololens/ServoApp/BrowserPage.h
index bae31d22ff3..1b4a53bc636 100644
--- a/support/hololens/ServoApp/BrowserPage.h
+++ b/support/hololens/ServoApp/BrowserPage.h
@@ -5,18 +5,20 @@
#pragma once
#include "BrowserPage.g.h"
-#include "ServoControl\ServoControl.h"
+#include "ServoControl/ServoControl.h"
+#include "Devtools/Client.h"
namespace winrt::ServoApp::implementation {
using namespace winrt::Windows;
+using namespace winrt::Windows::Data::Json;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::UI::Xaml;
static const hstring SERVO_SCHEME = L"fxr";
static const hstring SERVO_SCHEME_SLASH_SLASH = L"fxr://";
-struct BrowserPage : BrowserPageT<BrowserPage> {
+struct BrowserPage : BrowserPageT<BrowserPage>, public servo::DevtoolsDelegate {
public:
BrowserPage();
@@ -41,6 +43,8 @@ public:
RoutedEventArgs const &);
void OnPrefererenceSearchboxEdited(IInspectable const &,
Input::KeyRoutedEventArgs const &);
+ void OnDevtoolsMessage(servo::DevtoolsMessageLevel, hstring, hstring);
+ void OnDevtoolsDetached();
private:
void UpdatePref(ServoApp::Pref, Controls::Control);
@@ -48,6 +52,7 @@ private:
void BuildPrefList();
DevtoolsStatus mDevtoolsStatus = DevtoolsStatus::Stopped;
unsigned int mDevtoolsPort = 0;
+ std::unique_ptr<servo::DevtoolsClient> mDevtoolsClient;
};
} // namespace winrt::ServoApp::implementation
diff --git a/support/hololens/ServoApp/BrowserPage.xaml b/support/hololens/ServoApp/BrowserPage.xaml
index 7954ad828e4..bb034bba5fb 100644
--- a/support/hololens/ServoApp/BrowserPage.xaml
+++ b/support/hololens/ServoApp/BrowserPage.xaml
@@ -130,7 +130,7 @@
</TextBox.KeyboardAccelerators>
</TextBox>
<StackPanel Orientation="Horizontal" Grid.Column="2">
- <Button Style="{StaticResource NavigationBarButton}" x:Name="devtoolsButton" x:Uid="devtoolsButton" IsTabStop="true" Click="OnDevtoolsButtonClicked">
+ <Button Style="{StaticResource NavigationBarButton}" x:Name="devtoolsButton" IsEnabled="false" x:Uid="devtoolsButton" IsTabStop="true" Click="OnDevtoolsButtonClicked">
<Image Source="Assets/UI/devtools.png" Height="18"></Image>
</Button>
<ProgressRing x:Name="urlbarLoadingIndicator" Margin="10,0"/>
@@ -149,6 +149,12 @@
</Button>
</Grid>
</muxc:TabView.TabStripFooter>
+ <muxc:TabViewItem x:Uid="devtoolsTabConsole" IsClosable="False">
+ <ScrollViewer x:Name="DevtoolsConsoleScrollViewer" VerticalScrollMode="Enabled" HorizontalScrollMode="Enabled">
+ <RichTextBlock x:Name="DevtoolsConsoleOutput" FontSize="10" FontFamily="Consolas" LineHeight="14">
+ </RichTextBlock>
+ </ScrollViewer>
+ </muxc:TabViewItem>
<muxc:TabViewItem x:Uid="devtoolsTabServer" IsClosable="False">
<TextBlock x:Name="DevtoolsStatusMessage" Margin="10"></TextBlock>
</muxc:TabViewItem>
diff --git a/support/hololens/ServoApp/Devtools/Client.cpp b/support/hololens/ServoApp/Devtools/Client.cpp
new file mode 100644
index 00000000000..8a10ce65cdc
--- /dev/null
+++ b/support/hololens/ServoApp/Devtools/Client.cpp
@@ -0,0 +1,237 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "pch.h"
+#include "strutils.h"
+#include "Client.h"
+
+using namespace winrt::Windows::Data::Json;
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Windows::Networking;
+using namespace winrt::Windows::Storage::Streams;
+
+namespace winrt::servo {
+
+void DevtoolsClient::Stop() {
+ if (mReceiving && mReceiveOp.has_value() &&
+ mReceiveOp->Status() != AsyncStatus::Completed) {
+ mReceiveOp->Cancel();
+ }
+}
+
+void DevtoolsClient::Run() {
+ if (mReceiving) {
+ throw hresult_error(E_FAIL, L"Already running");
+ }
+ mReceiving = true;
+ auto socket = Sockets::StreamSocket();
+ auto hostname = HostName(mHostname);
+ auto connecting = socket.ConnectAsync(hostname, mPort);
+ connecting.Completed([=](const auto &, const auto &) {
+ mDataReader = DataReader(socket.InputStream());
+ mDataWriter = DataWriter(socket.OutputStream());
+ mReceiveOp = {Loop()};
+ mReceiveOp->Completed([=](const auto &, const auto &) {
+ mReceiveOp = {};
+ mDataReader->DetachStream();
+ mDataWriter->DetachStream();
+ mReceiving = false;
+ mDelegate.OnDevtoolsDetached();
+ });
+ });
+}
+
+IAsyncAction DevtoolsClient::Loop() {
+ auto cancellation = co_await winrt::get_cancellation_token();
+ cancellation.callback([=] {
+ if (mReaderOp.Status() != AsyncStatus::Completed) {
+ mReaderOp.Cancel();
+ }
+ });
+
+ while (!cancellation()) {
+ unsigned int len = 0;
+ while (!cancellation()) {
+ mReaderOp = mDataReader->LoadAsync(1);
+ co_await mReaderOp;
+ hstring c = mDataReader->ReadString(1);
+ if (c == L":")
+ break;
+ try {
+ unsigned int digit = std::stoi(c.c_str());
+ len = 10 * len + digit;
+ } catch (...) {
+ throw hresult_error(E_FAIL, L"Can't parse message header:" + c);
+ }
+ if (len >= 10000) {
+ throw hresult_error(E_FAIL, L"Message length too long");
+ }
+ }
+ if (cancellation()) {
+ break;
+ }
+ hstring request = L"";
+ mReaderOp = mDataReader->LoadAsync(len);
+ auto bytesLoaded = co_await mReaderOp;
+ request = request + mDataReader->ReadString(bytesLoaded);
+ JsonObject json;
+ if (!JsonObject::TryParse(request, json)) {
+ throw hresult_error(E_FAIL, L"Can't parse message: " + request);
+ }
+ HandleMessage(json);
+ }
+}
+
+void DevtoolsClient::HandleMessage(JsonObject obj) {
+ // Basic devtools protocol implementation:
+ // https://docs.firefox-dev.tools/backend/protocol.html
+
+ if (obj.HasKey(L"from") && obj.GetNamedString(L"from") == L"root") {
+ if (obj.HasKey(L"applicationType")) {
+ // First message. Ask for the current tab
+ JsonObject out;
+ out.Insert(L"to", JsonValue::CreateStringValue(L"root"));
+ out.Insert(L"type", JsonValue::CreateStringValue(L"getTab"));
+ Send(out);
+ return;
+ } else if (obj.HasKey(L"tab")) {
+ // Got the current tab.
+ auto tab = obj.GetNamedObject(L"tab");
+ if (tab.HasKey(L"actor")) {
+ // Attach to tab, and ask for cached messaged
+ JsonObject msg1;
+ msg1.Insert(L"to", tab.GetNamedValue(L"actor"));
+ msg1.Insert(L"type", JsonValue::CreateStringValue(L"attach"));
+ Send(msg1);
+ JsonObject msg2;
+ msg2.Insert(L"to", tab.GetNamedValue(L"consoleActor"));
+ msg2.Insert(L"type",
+ JsonValue::CreateStringValue(L"getCachedMessages"));
+ JsonArray types;
+ types.Append(JsonValue::CreateStringValue(L"PageError"));
+ types.Append(JsonValue::CreateStringValue(L"ConsoleAPI"));
+ msg2.Insert(L"messageTypes", types);
+ Send(msg2);
+ return;
+ }
+ }
+ } else if (obj.HasKey(L"type")) { // Not from root
+ if (obj.GetNamedString(L"type") == L"pageError") {
+ // Got a page error
+ HandlePageError(obj.GetNamedObject(L"pageError"));
+ return;
+ } else if (obj.GetNamedString(L"type") == L"consoleAPICall") {
+ // console.* calls
+ auto message = obj.GetNamedObject(L"message");
+ HandleConsoleMessage(message);
+ return;
+ } else if (obj.GetNamedString(L"type") == L"tabAttached") {
+ // Ignore
+ return;
+ } else if (obj.GetNamedString(L"type") == L"networkEvent") {
+ // Ignore
+ return;
+ } else if (obj.GetNamedString(L"type") == L"tabNavigated") {
+ // Ignore
+ return;
+ } else if (obj.GetNamedString(L"type") == L"networkEventUpdate") {
+ // FIXME: log if there is a non-200 HTTP response
+ return;
+ }
+ } else if (obj.HasKey(L"messages")) {
+ // Response to getCachedMessages
+ for (auto messageValue : obj.GetNamedArray(L"messages")) {
+ auto message = messageValue.GetObject();
+ if (message.GetNamedString(L"_type") == L"ConsoleAPI") {
+ HandleConsoleMessage(message);
+ } else if (message.GetNamedString(L"_type") == L"PageError") {
+ HandlePageError(message);
+ } else {
+ HandleNonHandledMessage(message);
+ }
+ }
+ return;
+ }
+ HandleNonHandledMessage(obj);
+}
+
+DevtoolsMessageLevel DevtoolsClient::ParseLevel(JsonObject message) {
+ if (message.GetNamedBoolean(L"error", false)) {
+ return DevtoolsMessageLevel::Error;
+ }
+ if (message.GetNamedBoolean(L"warning", false)) {
+ return DevtoolsMessageLevel::Warn;
+ }
+ if (message.GetNamedBoolean(L"exception", false)) {
+ return DevtoolsMessageLevel::Error;
+ }
+ auto level = message.GetNamedString(L"level", L"");
+ if (level == L"warn") {
+ return DevtoolsMessageLevel::Warn;
+ } else if (level == L"error") {
+ return DevtoolsMessageLevel::Error;
+ }
+ return DevtoolsMessageLevel::None;
+}
+
+hstring DevtoolsClient::ParseSource(JsonObject message) {
+ auto source = message.GetNamedString(L"filename", L"<>");
+ if (message.HasKey(L"lineNumber")) {
+ source = source + L":" + to_hstring(message.GetNamedNumber(L"lineNumber"));
+ }
+ if (message.HasKey(L"columnNumber")) {
+ source =
+ source + L":" + to_hstring(message.GetNamedNumber(L"columnNumber"));
+ }
+ return source;
+}
+
+void DevtoolsClient::HandlePageError(JsonObject message) {
+ auto source = ParseSource(message);
+ auto body = message.GetNamedString(L"errorMessage", L"");
+ auto level = ParseLevel(message);
+ mDelegate.OnDevtoolsMessage(level, source, body);
+}
+
+void DevtoolsClient::HandleConsoleMessage(JsonObject message) {
+ auto source = ParseSource(message);
+ auto level = ParseLevel(message);
+ hstring body = L"";
+ for (auto arg : message.GetNamedArray(L"arguments")) {
+ body = body + arg.Stringify();
+ }
+ mDelegate.OnDevtoolsMessage(level, source, body);
+}
+
+void DevtoolsClient::HandleNonHandledMessage(JsonObject message) {
+ auto level = DevtoolsMessageLevel::Warn;
+ auto body = L"Unhandled devtools message: " + message.Stringify();
+ mDelegate.OnDevtoolsMessage(level, L"", body);
+}
+
+void DevtoolsClient::SendPendingObjects() {
+ if (mPendingObjects.empty() || mSending) {
+ return;
+ }
+ mSending = true;
+ auto obj = mPendingObjects.front();
+ mPendingObjects.erase(mPendingObjects.begin());
+ hstring msg = obj.Stringify();
+ hstring size = to_hstring(msg.size());
+ hstring request = size + L":" + msg;
+ mDataWriter->WriteString(request);
+ mDataWriter->StoreAsync().Completed([=](const auto &, const auto &) {
+ mDataWriter->FlushAsync().Completed([=](const auto &, const auto &) {
+ mSending = false;
+ SendPendingObjects();
+ });
+ });
+}
+
+void DevtoolsClient::Send(JsonObject obj) {
+ mPendingObjects.push_back(obj);
+ SendPendingObjects();
+}
+
+} // namespace winrt::servo
diff --git a/support/hololens/ServoApp/Devtools/Client.h b/support/hololens/ServoApp/Devtools/Client.h
new file mode 100644
index 00000000000..162d2e33e07
--- /dev/null
+++ b/support/hololens/ServoApp/Devtools/Client.h
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#pragma once
+
+#include "pch.h"
+
+namespace winrt::servo {
+using namespace winrt::Windows::Storage::Streams;
+using namespace winrt::Windows::Data::Json;
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Windows::Networking::Sockets;
+
+class DevtoolsDelegate;
+
+enum DevtoolsMessageLevel { Error, Warn, None };
+
+class DevtoolsClient {
+
+public:
+ DevtoolsClient(hstring hostname, hstring port, DevtoolsDelegate &d)
+ : mDelegate(d), mHostname(hostname), mPort(port){};
+
+ ~DevtoolsClient() { Stop(); }
+ void Run();
+ void Stop();
+ void Send(JsonObject);
+
+private:
+ hstring mPort;
+ hstring mHostname;
+ DevtoolsDelegate &mDelegate;
+ std::optional<DataReader> mDataReader;
+ std::optional<DataWriter> mDataWriter;
+ std::optional<IAsyncAction> mReceiveOp;
+ std::vector<JsonObject> mPendingObjects;
+ IAsyncOperation<unsigned int> mReaderOp;
+ bool mSending = false;
+ bool mReceiving = false;
+ void SendPendingObjects();
+ IAsyncAction Loop();
+ DevtoolsMessageLevel ParseLevel(JsonObject);
+ hstring ParseSource(JsonObject);
+ void HandleMessage(JsonObject);
+ void HandlePageError(JsonObject);
+ void HandleConsoleMessage(JsonObject);
+ void HandleNonHandledMessage(JsonObject);
+};
+
+class DevtoolsDelegate {
+public:
+ virtual void OnDevtoolsMessage(DevtoolsMessageLevel, hstring, hstring) = 0;
+ virtual void OnDevtoolsDetached() = 0;
+};
+
+} // namespace winrt::servo
diff --git a/support/hololens/ServoApp/ServoApp.vcxproj b/support/hololens/ServoApp/ServoApp.vcxproj
index 040bd8cc7dc..0d819c3fffc 100644
--- a/support/hololens/ServoApp/ServoApp.vcxproj
+++ b/support/hololens/ServoApp/ServoApp.vcxproj
@@ -119,6 +119,7 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
+ <ClInclude Include="Devtools\Client.h" />
<ClInclude Include="strutils.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="App.h">
@@ -913,6 +914,7 @@
<Image Include="Assets\Wide310x150Logo.scale-400.png" />
</ItemGroup>
<ItemGroup>
+ <ClCompile Include="Devtools\Client.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
@@ -964,4 +966,4 @@
<Error Condition="!Exists('..\packages\ANGLE.WindowsStore.Servo.2.1.19\build\native\ANGLE.WindowsStore.Servo.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ANGLE.WindowsStore.Servo.2.1.19\build\native\ANGLE.WindowsStore.Servo.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.UI.Xaml.2.4.2\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.UI.Xaml.2.4.2\build\native\Microsoft.UI.Xaml.targets'))" />
</Target>
-</Project>
+</Project> \ No newline at end of file
diff --git a/support/hololens/ServoApp/ServoApp.vcxproj.filters b/support/hololens/ServoApp/ServoApp.vcxproj.filters
index 4348585239a..4bb5ad598a7 100644
--- a/support/hololens/ServoApp/ServoApp.vcxproj.filters
+++ b/support/hololens/ServoApp/ServoApp.vcxproj.filters
@@ -21,6 +21,9 @@
<ClCompile Include="ServoControl\ServoControl.cpp">
<Filter>ServoControl</Filter>
</ClCompile>
+ <ClCompile Include="Devtools\Client.cpp">
+ <Filter>Devtools</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@@ -37,6 +40,9 @@
</ClInclude>
<ClInclude Include="DefaultUrl.h" />
<ClInclude Include="strutils.h" />
+ <ClInclude Include="Devtools\Client.h">
+ <Filter>Devtools</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<Image Include="Assets\Wide310x150Logo.scale-200.png">
@@ -896,6 +902,9 @@
<Filter Include="Strings\fr-FR">
<UniqueIdentifier>{b7d3273d-a27c-4176-87a1-3d5222b796b3}</UniqueIdentifier>
</Filter>
+ <Filter Include="Devtools">
+ <UniqueIdentifier>{12da8b9d-c14a-4be1-8328-c4e729fdfd3b}</UniqueIdentifier>
+ </Filter>
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
diff --git a/support/hololens/ServoApp/Strings/en-US/Resources.resw b/support/hololens/ServoApp/Strings/en-US/Resources.resw
index 8f496bef051..b2a0052477a 100644
--- a/support/hololens/ServoApp/Strings/en-US/Resources.resw
+++ b/support/hololens/ServoApp/Strings/en-US/Resources.resw
@@ -111,6 +111,9 @@
<data name="devtoolsTabServer.[using:Microsoft.UI.Xaml.Controls]TabViewItem.Header" xml:space="preserve">
<value>Devtools Server</value>
</data>
+ <data name="devtoolsTabConsole.[using:Microsoft.UI.Xaml.Controls]TabViewItem.Header" xml:space="preserve">
+ <value>Console</value>
+ </data>
<data name="devtoolsTabPrefs.[using:Microsoft.UI.Xaml.Controls]TabViewItem.Header" xml:space="preserve">
<value>Preferences</value>
</data>
diff --git a/support/hololens/ServoApp/pch.h b/support/hololens/ServoApp/pch.h
index ff5db96a994..fe0197dd58d 100644
--- a/support/hololens/ServoApp/pch.h
+++ b/support/hololens/ServoApp/pch.h
@@ -14,6 +14,7 @@
#include <d3d11_4.h>
#include <DirectXColors.h>
#include <dwrite_2.h>
+#include <sstream>
#include <Windows.Graphics.Directx.Direct3D11.Interop.h>
#include <windows.h>
#include <WindowsNumerics.h>
@@ -36,6 +37,7 @@
#include <winrt/Windows.Gaming.Input.h>
#include <winrt/Windows.Graphics.Display.h>
#include <winrt/Windows.Graphics.Holographic.h>
+#include <winrt/Windows.Networking.Sockets.h>
#include <winrt/Windows.Perception.People.h>
#include <winrt/Windows.Perception.Spatial.h>
#include <winrt/Windows.Storage.h>
@@ -49,14 +51,19 @@
#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
#include <winrt/Windows.UI.Xaml.Data.h>
#include <winrt/Windows.UI.Xaml.h>
+#include <winrt/Windows.UI.Xaml.Documents.h>
#include <winrt/Windows.UI.Xaml.Input.h>
#include <winrt/Windows.UI.Xaml.Interop.h>
#include <winrt/Windows.UI.Xaml.Markup.h>
#include <winrt/Windows.UI.Xaml.Navigation.h>
#include <winrt/Windows.UI.Notifications.h>
+#include <winrt/Windows.UI.Text.h>
#include <winrt/Windows.Data.Xml.Dom.h>
+#include <winrt/Windows.Storage.Streams.h>
+#include <winrt/Windows.Data.Json.h>
-#include "winrt/Microsoft.UI.Xaml.Automation.Peers.h"
-#include "winrt/Microsoft.UI.Xaml.Controls.Primitives.h"
-#include "winrt/Microsoft.UI.Xaml.Media.h"
-#include "winrt/Microsoft.UI.Xaml.XamlTypeInfo.h" \ No newline at end of file
+#include <winrt/Microsoft.UI.Xaml.Automation.Peers.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
+#include <winrt/Microsoft.UI.Xaml.Media.h>
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>