From eacbc106c4603f6128d726ec0fdcb1cbfc6cab94 Mon Sep 17 00:00:00 2001 From: Aaron Clauson Date: Thu, 28 May 2020 22:54:18 +0100 Subject: [PATCH] Mjpeg streaming for terminal control background. --- src/cascadia/TerminalControl/TermControl.cpp | 73 ++- src/cascadia/TerminalControl/TermControl.h | 6 + .../TerminalControl/TerminalControl.vcxproj | 10 +- .../TerminalControl.vcxproj.filters | 12 +- src/cascadia/TerminalControl/mjpeg.h | 472 ++++++++++++++++++ src/cascadia/TerminalControl/rtpsocket.cpp | 178 +++++++ src/cascadia/TerminalControl/rtpsocket.h | 90 ++++ 7 files changed, 829 insertions(+), 12 deletions(-) create mode 100644 src/cascadia/TerminalControl/mjpeg.h create mode 100644 src/cascadia/TerminalControl/rtpsocket.cpp create mode 100644 src/cascadia/TerminalControl/rtpsocket.h diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 0d2530ae9791..b9912925c9a3 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -11,9 +11,14 @@ #include #include #include "..\..\types\inc\GlyphWidth.hpp" +#include +#include #include "TermControl.g.cpp" #include "TermControlAutomationPeer.h" +#include +#include +#include using namespace ::Microsoft::Console::Types; using namespace ::Microsoft::Terminal::Core; @@ -369,7 +374,27 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation if (!_settings.BackgroundImage().empty()) { - Windows::Foundation::Uri imageUri{ _settings.BackgroundImage() }; + // Check if an MJPEG path has been requested. + std::string backgroundPath = to_string(_settings.BackgroundImage()); + std::string regexInput = backgroundPath; + const std::regex pathRegex("(.*);mjpeg:(\\d+)$", std::regex_constants::icase); + std::smatch pathMatch; + + if (std::regex_match(regexInput, pathMatch, pathRegex)) + { + if (pathMatch.size() == 3) + { + backgroundPath = pathMatch[1].str(); + int listenPort = std::stoi(pathMatch[2].str()); + + _rtpSocket = std::make_unique(listenPort); + auto fp = std::bind(&TermControl::_OnRtpFrameReady, this, std::placeholders::_1); + _rtpSocket->SetFrameReadyCallback(fp); + } + } + + // TODO: CHeck the MJPEG string checking doesn't lose Unicode chars. + Windows::Foundation::Uri imageUri{ to_hstring(backgroundPath) }; // Check if the image brush is already pointing to the image // in the modified settings; if it isn't (or isn't there), @@ -393,6 +418,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation BackgroundImage().Opacity(_settings.BackgroundImageOpacity()); BackgroundImage().HorizontalAlignment(_settings.BackgroundImageHorizontalAlignment()); BackgroundImage().VerticalAlignment(_settings.BackgroundImageVerticalAlignment()); + + if (_rtpSocket != nullptr) + { + _rtpSocket->Start(); + } } else { @@ -1528,6 +1558,42 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } } + // Method Description: + // Callback function for MJPEG receiver when a full JPEG frame is ready. + void TermControl::_OnRtpFrameReady(std::vector& jpeg) + { + _SetBackgroundImage(jpeg); + } + + // Method Description: + // Sets the terminal control background to the JPEG image contained in the supplied + // vector. + winrt::fire_and_forget TermControl::_SetBackgroundImage(std::vector jpeg) + { + co_await winrt::resume_foreground(Dispatcher()); + + Windows::Storage::Streams::InMemoryRandomAccessStream jpegStream; + auto writer = Windows::Storage::Streams::DataWriter(jpegStream.GetOutputStreamAt(0)); + writer.WriteBytes(jpeg); + co_await writer.StoreAsync(); + + auto decoder = co_await Windows::Graphics::Imaging::BitmapDecoder::CreateAsync(jpegStream); + + auto bmp = co_await decoder.GetSoftwareBitmapAsync(); + + if (bmp.BitmapPixelFormat() != Windows::Graphics::Imaging::BitmapPixelFormat::Bgra8 || + bmp.BitmapAlphaMode() == Windows::Graphics::Imaging::BitmapAlphaMode::Straight) + { + bmp = Windows::Graphics::Imaging::SoftwareBitmap::Convert(bmp, + Windows::Graphics::Imaging::BitmapPixelFormat::Bgra8, + Windows::Graphics::Imaging::BitmapAlphaMode::Premultiplied); + } + + Windows::UI::Xaml::Media::Imaging::SoftwareBitmapSource source; + co_await source.SetBitmapAsync(bmp); + BackgroundImage().Source(source); + } + // Method Description: // - Event handler for the GotFocus event. This is used to... // - enable accessibility notifications for this TermControl @@ -2135,6 +2201,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation TSFInputControl().Close(); // Disconnect the TSF input control so it doesn't receive EditContext events. _autoScrollTimer.Stop(); + if (_rtpSocket != nullptr) + { + _rtpSocket->Close(); + } + // GH#1996 - Close the connection asynchronously on a background // thread. // Since TermControl::Close is only ever triggered by the UI, we diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index ecb7c52cb02a..f474119198f3 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -15,6 +15,7 @@ #include "../buffer/out/search.h" #include "cppwinrt_utils.h" #include "SearchBoxControl.h" +#include "rtpsocket.h" namespace winrt::Microsoft::Terminal::TerminalControl::implementation { @@ -242,6 +243,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // Unbounded main dispatcher use leads to massive memory leaks and intense slowdowns // on the UI thread. std::atomic _coroutineDispatchStateUpdateInProgress{ false }; + + // Socket and callbacks used for MJPEG background on terminal control. + std::unique_ptr _rtpSocket{ nullptr }; + void _OnRtpFrameReady(std::vector& jpeg); + winrt::fire_and_forget _SetBackgroundImage(std::vector jpeg); }; } diff --git a/src/cascadia/TerminalControl/TerminalControl.vcxproj b/src/cascadia/TerminalControl/TerminalControl.vcxproj index 1a664a089834..7e81ec0d3ec1 100644 --- a/src/cascadia/TerminalControl/TerminalControl.vcxproj +++ b/src/cascadia/TerminalControl/TerminalControl.vcxproj @@ -19,7 +19,6 @@ projects compile properly when they depend on this "Microsoft.winmd." --> 3 -