diff --git a/application/remote_desk/demo/App.cpp b/application/remote_desk/demo/App.cpp new file mode 100644 index 0000000..a4b8f3c --- /dev/null +++ b/application/remote_desk/demo/App.cpp @@ -0,0 +1,85 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THE SOFTWARE IS PROVIDED “AS IS? WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH +// THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//********************************************************* + +#include "pch.h" +#include "App.h" +#include "SimpleCapture.h" + +using namespace winrt; +using namespace Windows::System; +using namespace Windows::Foundation; +using namespace Windows::UI; +using namespace Windows::UI::Composition; +using namespace Windows::Graphics::Capture; + +void App::Initialize(ContainerVisual const &root) { + auto queue = DispatcherQueue::GetForCurrentThread(); + + m_compositor = root.Compositor(); + m_root = m_compositor.CreateContainerVisual(); + m_content = m_compositor.CreateSpriteVisual(); + m_brush = m_compositor.CreateSurfaceBrush(); + + m_root.RelativeSizeAdjustment({1, 1}); + root.Children().InsertAtTop(m_root); + + m_content.AnchorPoint({0.5f, 0.5f}); + m_content.RelativeOffsetAdjustment({0.5f, 0.5f, 0}); + m_content.RelativeSizeAdjustment({1, 1}); + m_content.Size({-80, -80}); + m_content.Brush(m_brush); + m_brush.HorizontalAlignmentRatio(0.5f); + m_brush.VerticalAlignmentRatio(0.5f); + m_brush.Stretch(CompositionStretch::Uniform); + auto shadow = m_compositor.CreateDropShadow(); + shadow.Mask(m_brush); + m_content.Shadow(shadow); + m_root.Children().InsertAtTop(m_content); + + auto d3dDevice = CreateD3DDevice(); + auto dxgiDevice = d3dDevice.as(); + m_device = CreateDirect3DDevice(dxgiDevice.get()); +} + +void App::StartCapture(HWND hwnd) { + if (m_capture) { + m_capture->Close(); + m_capture = nullptr; + } + + auto item = CreateCaptureItemForWindow(hwnd); + + m_capture = std::make_unique(m_device, item); + + auto surface = m_capture->CreateSurface(m_compositor); + m_brush.Surface(surface); + + m_capture->StartCapture(); +} + +void App::StartCapture(HMONITOR hmonitor) { + if (m_capture) { + m_capture->Close(); + m_capture = nullptr; + } + + auto item = CreateCaptureItemForMonitor(hmonitor); + + m_capture = std::make_unique(m_device, item); + + auto surface = m_capture->CreateSurface(m_compositor); + m_brush.Surface(surface); + + m_capture->StartCapture(); +} \ No newline at end of file diff --git a/application/remote_desk/demo/App.h b/application/remote_desk/demo/App.h new file mode 100644 index 0000000..95cc2de --- /dev/null +++ b/application/remote_desk/demo/App.h @@ -0,0 +1,24 @@ +#pragma once + +class SimpleCapture; + +class App { +public: + App() {} + ~App() {} + + void Initialize(winrt::Windows::UI::Composition::ContainerVisual const &root); + + void StartCapture(HWND hwnd); + void StartCapture(HMONITOR hmonitor); + +private: + winrt::Windows::UI::Composition::Compositor m_compositor{nullptr}; + winrt::Windows::UI::Composition::ContainerVisual m_root{nullptr}; + winrt::Windows::UI::Composition::SpriteVisual m_content{nullptr}; + winrt::Windows::UI::Composition::CompositionSurfaceBrush m_brush{nullptr}; + + winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice m_device{ + nullptr}; + std::unique_ptr m_capture{nullptr}; +}; \ No newline at end of file diff --git a/application/remote_desk/demo/SimpleCapture.cpp b/application/remote_desk/demo/SimpleCapture.cpp new file mode 100644 index 0000000..d927d5f --- /dev/null +++ b/application/remote_desk/demo/SimpleCapture.cpp @@ -0,0 +1,219 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THE SOFTWARE IS PROVIDED “AS IS? WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH +// THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//********************************************************* + +#include "pch.h" +#include "SimpleCapture.h" + +using namespace winrt; +using namespace Windows; +using namespace Windows::Foundation; +using namespace Windows::System; +using namespace Windows::Graphics; +using namespace Windows::Graphics::Capture; +using namespace Windows::Graphics::DirectX; +using namespace Windows::Graphics::DirectX::Direct3D11; +using namespace Windows::Foundation::Numerics; +using namespace Windows::UI; +using namespace Windows::UI::Composition; + +SimpleCapture::SimpleCapture(IDirect3DDevice const &device, + GraphicsCaptureItem const &item) { + m_item = item; + m_device = device; + + // Set up + auto d3dDevice = GetDXGIInterfaceFromObject(m_device); + d3dDevice->GetImmediateContext(m_d3dContext.put()); + + auto size = m_item.Size(); + + m_swapChain = CreateDXGISwapChain( + d3dDevice, static_cast(size.Width), + static_cast(size.Height), + static_cast(DirectXPixelFormat::B8G8R8A8UIntNormalized), 2); + + // Create framepool, define pixel format (DXGI_FORMAT_B8G8R8A8_UNORM), and + // frame size. + m_framePool = Direct3D11CaptureFramePool::Create( + m_device, DirectXPixelFormat::B8G8R8A8UIntNormalized, 2, size); + m_session = m_framePool.CreateCaptureSession(m_item); + m_lastSize = size; + m_frameArrived = m_framePool.FrameArrived( + auto_revoke, {this, &SimpleCapture::OnFrameArrived}); +} + +// Start sending capture frames +void SimpleCapture::StartCapture() { + CheckClosed(); + m_session.StartCapture(); +} + +ICompositionSurface SimpleCapture::CreateSurface(Compositor const &compositor) { + CheckClosed(); + return CreateCompositionSurfaceForSwapChain(compositor, m_swapChain.get()); +} + +// Process captured frames +void SimpleCapture::Close() { + auto expected = false; + if (m_closed.compare_exchange_strong(expected, true)) { + m_frameArrived.revoke(); + m_framePool.Close(); + m_session.Close(); + + m_swapChain = nullptr; + m_framePool = nullptr; + m_session = nullptr; + m_item = nullptr; + } +} + +void SimpleCapture::OnFrameArrived( + Direct3D11CaptureFramePool const &sender, + winrt::Windows::Foundation::IInspectable const &) { + auto newSize = false; + + { + auto frame = sender.TryGetNextFrame(); + auto frameContentSize = frame.ContentSize(); + + if (frameContentSize.Width != m_lastSize.Width || + frameContentSize.Height != m_lastSize.Height) { + // The thing we have been capturing has changed size. + // We need to resize our swap chain first, then blit the pixels. + // After we do that, retire the frame and then recreate our frame pool. + newSize = true; + m_lastSize = frameContentSize; + m_swapChain->ResizeBuffers( + 2, static_cast(m_lastSize.Width), + static_cast(m_lastSize.Height), + static_cast(DirectXPixelFormat::B8G8R8A8UIntNormalized), + 0); + } + + // copy to swapChain + { + auto frameSurface = + GetDXGIInterfaceFromObject(frame.Surface()); + + + com_ptr backBuffer; + check_hresult(m_swapChain->GetBuffer(0, guid_of(), + backBuffer.put_void())); + + m_d3dContext->CopyResource(backBuffer.get(), frameSurface.get()); + + DXGI_PRESENT_PARAMETERS presentParameters = {0}; + m_swapChain->Present1(1, 0, &presentParameters); + } + + // copy to mapped texture + { + auto frameSurface = + GetDXGIInterfaceFromObject(frame.Surface()); + + if (!m_mappedTexture || newSize) + CreateMappedTexture(frameSurface); + + m_d3dContext->CopyResource(m_mappedTexture.get(), frameSurface.get()); + + D3D11_MAPPED_SUBRESOURCE mapInfo; + m_d3dContext->Map(m_mappedTexture.get(), 0, D3D11_MAP_READ, + D3D11_MAP_FLAG_DO_NOT_WAIT, &mapInfo); + + // copy data from mapInfo.pData +#if 1 + if (mapInfo.pData) { + static unsigned char *buffer = nullptr; + if (buffer && newSize) + delete[] buffer; + + if (!buffer) + buffer = new unsigned char[frameContentSize.Width * + frameContentSize.Height * 4]; + + int dstRowPitch = frameContentSize.Width * 4; + for (int h = 0; h < frameContentSize.Height; h++) { + memcpy_s(buffer + h * dstRowPitch, dstRowPitch, + (BYTE *)mapInfo.pData + h * mapInfo.RowPitch, + min(mapInfo.RowPitch, dstRowPitch)); + } + + BITMAPINFOHEADER bi; + + bi.biSize = sizeof(BITMAPINFOHEADER); + bi.biWidth = frameContentSize.Width; + bi.biHeight = frameContentSize.Height * (-1); + bi.biPlanes = 1; + bi.biBitCount = 32; // should get from system color bits + bi.biCompression = BI_RGB; + bi.biSizeImage = 0; + bi.biXPelsPerMeter = 0; + bi.biYPelsPerMeter = 0; + bi.biClrUsed = 0; + bi.biClrImportant = 0; + + BITMAPFILEHEADER bf; + bf.bfType = 0x4d42; + bf.bfReserved1 = 0; + bf.bfReserved2 = 0; + bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); + bf.bfSize = + bf.bfOffBits + frameContentSize.Width * frameContentSize.Height * 4; + + FILE *fp = nullptr; + + fopen_s(&fp, ".\\save.bmp", "wb+"); + + fwrite(&bf, 1, sizeof(bf), fp); + fwrite(&bi, 1, sizeof(bi), fp); + fwrite(buffer, 1, frameContentSize.Width * frameContentSize.Height * 4, + fp); + + fflush(fp); + fclose(fp); + } +#endif + + m_d3dContext->Unmap(m_mappedTexture.get(), 0); + } + } + + if (newSize) { + m_framePool.Recreate(m_device, DirectXPixelFormat::B8G8R8A8UIntNormalized, + 2, m_lastSize); + } +} + +HRESULT +SimpleCapture::CreateMappedTexture(winrt::com_ptr src_texture, + UINT width, UINT height) { + D3D11_TEXTURE2D_DESC src_desc; + src_texture->GetDesc(&src_desc); + D3D11_TEXTURE2D_DESC map_desc; + map_desc.Width = width == 0 ? src_desc.Width : width; + map_desc.Height = height == 0 ? src_desc.Height : height; + map_desc.MipLevels = src_desc.MipLevels; + map_desc.ArraySize = src_desc.ArraySize; + map_desc.Format = src_desc.Format; + map_desc.SampleDesc = src_desc.SampleDesc; + map_desc.Usage = D3D11_USAGE_STAGING; + map_desc.BindFlags = 0; + map_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + map_desc.MiscFlags = 0; + + auto d3dDevice = GetDXGIInterfaceFromObject(m_device); + + return d3dDevice->CreateTexture2D(&map_desc, nullptr, m_mappedTexture.put()); +} diff --git a/application/remote_desk/demo/SimpleCapture.h b/application/remote_desk/demo/SimpleCapture.h new file mode 100644 index 0000000..ce66db3 --- /dev/null +++ b/application/remote_desk/demo/SimpleCapture.h @@ -0,0 +1,49 @@ +#pragma once + +class SimpleCapture { +public: + SimpleCapture( + winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice const + &device, + winrt::Windows::Graphics::Capture::GraphicsCaptureItem const &item); + ~SimpleCapture() { Close(); } + + void StartCapture(); + winrt::Windows::UI::Composition::ICompositionSurface + CreateSurface(winrt::Windows::UI::Composition::Compositor const &compositor); + + void Close(); + +private: + void OnFrameArrived( + winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const + &sender, + winrt::Windows::Foundation::IInspectable const &args); + + void CheckClosed() { + if (m_closed.load() == true) { + throw winrt::hresult_error(RO_E_CLOSED); + } + } + + HRESULT + CreateMappedTexture(winrt::com_ptr src_texture, + UINT width = 0, UINT height = 0); + +private: + winrt::Windows::Graphics::Capture::GraphicsCaptureItem m_item{nullptr}; + winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool m_framePool{ + nullptr}; + winrt::Windows::Graphics::Capture::GraphicsCaptureSession m_session{nullptr}; + winrt::Windows::Graphics::SizeInt32 m_lastSize; + + winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice m_device{ + nullptr}; + winrt::com_ptr m_swapChain{nullptr}; + winrt::com_ptr m_d3dContext{nullptr}; + winrt::com_ptr m_mappedTexture{nullptr}; + + std::atomic m_closed = false; + winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool:: + FrameArrived_revoker m_frameArrived; +}; \ No newline at end of file diff --git a/application/remote_desk/demo/Win32MonitorEnumeration.h b/application/remote_desk/demo/Win32MonitorEnumeration.h new file mode 100644 index 0000000..e9e7bca --- /dev/null +++ b/application/remote_desk/demo/Win32MonitorEnumeration.h @@ -0,0 +1,50 @@ +#pragma once +#include + +struct Monitor { +public: + Monitor(nullptr_t) {} + Monitor(HMONITOR hmonitor, std::wstring &className, bool isPrimary) { + m_hmonitor = hmonitor; + m_className = className; + m_bIsPrimary = isPrimary; + } + + HMONITOR Hmonitor() const noexcept { return m_hmonitor; } + std::wstring ClassName() const noexcept { return m_className; } + bool IsPrimary() const noexcept { return m_bIsPrimary; } + +private: + HMONITOR m_hmonitor; + std::wstring m_className; + bool m_bIsPrimary; +}; + +BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, HDC hdc, LPRECT lprc, + LPARAM data) { + + MONITORINFOEX info_ex; + info_ex.cbSize = sizeof(MONITORINFOEX); + + GetMonitorInfo(hmonitor, &info_ex); + + if (info_ex.dwFlags == DISPLAY_DEVICE_MIRRORING_DRIVER) + return true; + + auto monitors = ((std::vector *)data); + std::wstring name = info_ex.szDevice; + auto monitor = + Monitor(hmonitor, name, info_ex.dwFlags & MONITORINFOF_PRIMARY); + + monitors->emplace_back(monitor); + + return true; +} + +std::vector EnumerateMonitors() { + std::vector monitors; + + ::EnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&monitors); + + return monitors; +} \ No newline at end of file diff --git a/application/remote_desk/demo/Win32WindowEnumeration.h b/application/remote_desk/demo/Win32WindowEnumeration.h new file mode 100644 index 0000000..20761c2 --- /dev/null +++ b/application/remote_desk/demo/Win32WindowEnumeration.h @@ -0,0 +1,101 @@ +#pragma once +#include + +struct Window { +public: + Window(nullptr_t) {} + Window(HWND hwnd, std::wstring const &title, std::wstring &className) { + m_hwnd = hwnd; + m_title = title; + m_className = className; + } + + HWND Hwnd() const noexcept { return m_hwnd; } + std::wstring Title() const noexcept { return m_title; } + std::wstring ClassName() const noexcept { return m_className; } + +private: + HWND m_hwnd; + std::wstring m_title; + std::wstring m_className; +}; + +std::wstring GetClassName(HWND hwnd) { + std::array className; + + ::GetClassName(hwnd, className.data(), (int)className.size()); + + std::wstring title(className.data()); + return title; +} + +std::wstring GetWindowText(HWND hwnd) { + std::array windowText; + + ::GetWindowText(hwnd, windowText.data(), (int)windowText.size()); + + std::wstring title(windowText.data()); + return title; +} + +bool IsAltTabWindow(Window const &window) { + HWND hwnd = window.Hwnd(); + HWND shellWindow = GetShellWindow(); + + auto title = window.Title(); + auto className = window.ClassName(); + + if (hwnd == shellWindow) { + return false; + } + + if (title.length() == 0) { + return false; + } + + if (!IsWindowVisible(hwnd)) { + return false; + } + + if (GetAncestor(hwnd, GA_ROOT) != hwnd) { + return false; + } + + LONG style = GetWindowLong(hwnd, GWL_STYLE); + if (!((style & WS_DISABLED) != WS_DISABLED)) { + return false; + } + + DWORD cloaked = FALSE; + HRESULT hrTemp = + DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloaked, sizeof(cloaked)); + if (SUCCEEDED(hrTemp) && cloaked == DWM_CLOAKED_SHELL) { + return false; + } + + return true; +} + +BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) { + auto class_name = GetClassName(hwnd); + auto title = GetWindowText(hwnd); + + auto window = Window(hwnd, title, class_name); + + if (!IsAltTabWindow(window)) { + return TRUE; + } + + std::vector &windows = + *reinterpret_cast *>(lParam); + windows.push_back(window); + + return TRUE; +} + +const std::vector EnumerateWindows() { + std::vector windows; + EnumWindows(EnumWindowsProc, reinterpret_cast(&windows)); + + return windows; +} \ No newline at end of file diff --git a/application/remote_desk/demo/capture.interop.h b/application/remote_desk/demo/capture.interop.h new file mode 100644 index 0000000..2232672 --- /dev/null +++ b/application/remote_desk/demo/capture.interop.h @@ -0,0 +1,28 @@ +#pragma once +#include +#include +#include + +inline auto CreateCaptureItemForWindow(HWND hwnd) { + auto activation_factory = winrt::get_activation_factory< + winrt::Windows::Graphics::Capture::GraphicsCaptureItem>(); + auto interop_factory = activation_factory.as(); + winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr}; + interop_factory->CreateForWindow( + hwnd, + winrt::guid_of(), + reinterpret_cast(winrt::put_abi(item))); + return item; +} + +inline auto CreateCaptureItemForMonitor(HMONITOR hmonitor) { + auto activation_factory = winrt::get_activation_factory< + winrt::Windows::Graphics::Capture::GraphicsCaptureItem>(); + auto interop_factory = activation_factory.as(); + winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr}; + interop_factory->CreateForMonitor( + hmonitor, + winrt::guid_of(), + reinterpret_cast(winrt::put_abi(item))); + return item; +} \ No newline at end of file diff --git a/application/remote_desk/demo/composition.interop.h b/application/remote_desk/demo/composition.interop.h new file mode 100644 index 0000000..01a10ce --- /dev/null +++ b/application/remote_desk/demo/composition.interop.h @@ -0,0 +1,69 @@ +#pragma once +#include +#include +#include + +inline auto CreateCompositionGraphicsDevice( + winrt::Windows::UI::Composition::Compositor const &compositor, + ::IUnknown *device) { + winrt::Windows::UI::Composition::CompositionGraphicsDevice graphicsDevice{ + nullptr}; + auto compositorInterop = + compositor.as(); + winrt::com_ptr + graphicsInterop; + winrt::check_hresult( + compositorInterop->CreateGraphicsDevice(device, graphicsInterop.put())); + winrt::check_hresult(graphicsInterop->QueryInterface( + winrt::guid_of< + winrt::Windows::UI::Composition::CompositionGraphicsDevice>(), + reinterpret_cast(winrt::put_abi(graphicsDevice)))); + return graphicsDevice; +} + +inline void ResizeSurface( + winrt::Windows::UI::Composition::CompositionDrawingSurface const &surface, + winrt::Windows::Foundation::Size const &size) { + auto surfaceInterop = surface.as< + ABI::Windows::UI::Composition::ICompositionDrawingSurfaceInterop>(); + SIZE newSize = {}; + newSize.cx = static_cast(std::round(size.Width)); + newSize.cy = static_cast(std::round(size.Height)); + winrt::check_hresult(surfaceInterop->Resize(newSize)); +} + +inline auto SurfaceBeginDraw( + winrt::Windows::UI::Composition::CompositionDrawingSurface const &surface) { + auto surfaceInterop = surface.as< + ABI::Windows::UI::Composition::ICompositionDrawingSurfaceInterop>(); + winrt::com_ptr context; + POINT offset = {}; + winrt::check_hresult(surfaceInterop->BeginDraw( + nullptr, __uuidof(ID2D1DeviceContext), context.put_void(), &offset)); + context->SetTransform( + D2D1::Matrix3x2F::Translation((FLOAT)offset.x, (FLOAT)offset.y)); + return context; +} + +inline void SurfaceEndDraw( + winrt::Windows::UI::Composition::CompositionDrawingSurface const &surface) { + auto surfaceInterop = surface.as< + ABI::Windows::UI::Composition::ICompositionDrawingSurfaceInterop>(); + winrt::check_hresult(surfaceInterop->EndDraw()); +} + +inline auto CreateCompositionSurfaceForSwapChain( + winrt::Windows::UI::Composition::Compositor const &compositor, + ::IUnknown *swapChain) { + winrt::Windows::UI::Composition::ICompositionSurface surface{nullptr}; + auto compositorInterop = + compositor.as(); + winrt::com_ptr + surfaceInterop; + winrt::check_hresult(compositorInterop->CreateCompositionSurfaceForSwapChain( + swapChain, surfaceInterop.put())); + winrt::check_hresult(surfaceInterop->QueryInterface( + winrt::guid_of(), + reinterpret_cast(winrt::put_abi(surface)))); + return surface; +} \ No newline at end of file diff --git a/application/remote_desk/demo/d3dHelpers.h b/application/remote_desk/demo/d3dHelpers.h new file mode 100644 index 0000000..c5d87f5 --- /dev/null +++ b/application/remote_desk/demo/d3dHelpers.h @@ -0,0 +1,132 @@ +#pragma once +#include "composition.interop.h" + +struct SurfaceContext { +public: + SurfaceContext(std::nullptr_t) {} + SurfaceContext( + winrt::Windows::UI::Composition::CompositionDrawingSurface surface) { + m_surface = surface; + m_d2dContext = SurfaceBeginDraw(m_surface); + } + ~SurfaceContext() { + SurfaceEndDraw(m_surface); + m_d2dContext = nullptr; + m_surface = nullptr; + } + + winrt::com_ptr GetDeviceContext() { return m_d2dContext; } + +private: + winrt::com_ptr m_d2dContext; + winrt::Windows::UI::Composition::CompositionDrawingSurface m_surface{nullptr}; +}; + +struct D3D11DeviceLock { +public: + D3D11DeviceLock(std::nullopt_t) {} + D3D11DeviceLock(ID3D11Multithread *pMultithread) { + m_multithread.copy_from(pMultithread); + m_multithread->Enter(); + } + ~D3D11DeviceLock() { + m_multithread->Leave(); + m_multithread = nullptr; + } + +private: + winrt::com_ptr m_multithread; +}; + +inline auto CreateWICFactory() { + winrt::com_ptr wicFactory; + winrt::check_hresult(::CoCreateInstance( + CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, + winrt::guid_of(), wicFactory.put_void())); + + return wicFactory; +} + +inline auto CreateD2DDevice(winrt::com_ptr const &factory, + winrt::com_ptr const &device) { + winrt::com_ptr result; + winrt::check_hresult( + factory->CreateDevice(device.as().get(), result.put())); + return result; +} + +inline auto CreateD3DDevice(D3D_DRIVER_TYPE const type, + winrt::com_ptr &device) { + WINRT_ASSERT(!device); + + UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + + //#ifdef _DEBUG + // flags |= D3D11_CREATE_DEVICE_DEBUG; + //#endif + + return D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0, + D3D11_SDK_VERSION, device.put(), nullptr, nullptr); +} + +inline auto CreateD3DDevice() { + winrt::com_ptr device; + HRESULT hr = CreateD3DDevice(D3D_DRIVER_TYPE_HARDWARE, device); + + if (DXGI_ERROR_UNSUPPORTED == hr) { + hr = CreateD3DDevice(D3D_DRIVER_TYPE_WARP, device); + } + + winrt::check_hresult(hr); + return device; +} + +inline auto CreateD2DFactory() { + D2D1_FACTORY_OPTIONS options{}; + + //#ifdef _DEBUG + // options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION; + //#endif + + winrt::com_ptr factory; + + winrt::check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, + options, factory.put())); + + return factory; +} + +inline auto CreateDXGISwapChain(winrt::com_ptr const &device, + const DXGI_SWAP_CHAIN_DESC1 *desc) { + auto dxgiDevice = device.as(); + winrt::com_ptr adapter; + winrt::check_hresult(dxgiDevice->GetParent(winrt::guid_of(), + adapter.put_void())); + winrt::com_ptr factory; + winrt::check_hresult( + adapter->GetParent(winrt::guid_of(), factory.put_void())); + + winrt::com_ptr swapchain; + winrt::check_hresult(factory->CreateSwapChainForComposition( + device.get(), desc, nullptr, swapchain.put())); + + return swapchain; +} + +inline auto CreateDXGISwapChain(winrt::com_ptr const &device, + uint32_t width, uint32_t height, + DXGI_FORMAT format, uint32_t bufferCount) { + DXGI_SWAP_CHAIN_DESC1 desc = {}; + desc.Width = width; + desc.Height = height; + desc.Format = format; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.BufferCount = bufferCount; + desc.Scaling = DXGI_SCALING_STRETCH; + desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; + + return CreateDXGISwapChain(device, &desc); +} \ No newline at end of file diff --git a/application/remote_desk/demo/direct3d11.interop.h b/application/remote_desk/demo/direct3d11.interop.h new file mode 100644 index 0000000..d282bbe --- /dev/null +++ b/application/remote_desk/demo/direct3d11.interop.h @@ -0,0 +1,41 @@ +#pragma once +#include + +extern "C" { +HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice( + ::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice); + +HRESULT __stdcall CreateDirect3D11SurfaceFromDXGISurface( + ::IDXGISurface *dgxiSurface, ::IInspectable **graphicsSurface); +} + +struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")) + IDirect3DDxgiInterfaceAccess : ::IUnknown { + virtual HRESULT __stdcall GetInterface(GUID const &id, void **object) = 0; +}; + +inline auto CreateDirect3DDevice(IDXGIDevice *dxgi_device) { + winrt::com_ptr<::IInspectable> d3d_device; + winrt::check_hresult( + CreateDirect3D11DeviceFromDXGIDevice(dxgi_device, d3d_device.put())); + return d3d_device + .as(); +} + +inline auto CreateDirect3DSurface(IDXGISurface *dxgi_surface) { + winrt::com_ptr<::IInspectable> d3d_surface; + winrt::check_hresult( + CreateDirect3D11SurfaceFromDXGISurface(dxgi_surface, d3d_surface.put())); + return d3d_surface + .as(); +} + +template +auto GetDXGIInterfaceFromObject( + winrt::Windows::Foundation::IInspectable const &object) { + auto access = object.as(); + winrt::com_ptr result; + winrt::check_hresult( + access->GetInterface(winrt::guid_of(), result.put_void())); + return result; +} \ No newline at end of file diff --git a/application/remote_desk/demo/main.cpp b/application/remote_desk/demo/main.cpp new file mode 100644 index 0000000..242912b --- /dev/null +++ b/application/remote_desk/demo/main.cpp @@ -0,0 +1,159 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THE SOFTWARE IS PROVIDED “AS IS? WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH +// THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//********************************************************* + +#include "pch.h" +#include "App.h" +#include "SimpleCapture.h" +#include "Win32MonitorEnumeration.h" +#include "Win32WindowEnumeration.h" +#include + +using namespace winrt; +using namespace Windows::UI; +using namespace Windows::UI::Composition; +using namespace Windows::UI::Composition::Desktop; + +// Direct3D11CaptureFramePool requires a DispatcherQueue +auto CreateDispatcherQueueController() { + namespace abi = ABI::Windows::System; + + DispatcherQueueOptions options{sizeof(DispatcherQueueOptions), + DQTYPE_THREAD_CURRENT, DQTAT_COM_STA}; + + Windows::System::DispatcherQueueController controller{nullptr}; + check_hresult(CreateDispatcherQueueController( + options, reinterpret_cast( + put_abi(controller)))); + return controller; +} + +DesktopWindowTarget CreateDesktopWindowTarget(Compositor const &compositor, + HWND window) { + namespace abi = ABI::Windows::UI::Composition::Desktop; + + auto interop = compositor.as(); + DesktopWindowTarget target{nullptr}; + check_hresult(interop->CreateDesktopWindowTarget( + window, true, + reinterpret_cast(put_abi(target)))); + return target; +} + +int CALLBACK WinMain(HINSTANCE instance, HINSTANCE previousInstance, + LPSTR cmdLine, int cmdShow); + +auto g_app = std::make_shared(); +auto g_windows = EnumerateWindows(); +auto g_monitors = EnumerateMonitors(); + +LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + +int CALLBACK WinMain(HINSTANCE instance, HINSTANCE previousInstance, + LPSTR cmdLine, int cmdShow) { + // Init COM + init_apartment(apartment_type::single_threaded); + + // Create the window + WNDCLASSEX wcex = {}; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = instance; + wcex.hIcon = LoadIcon(instance, MAKEINTRESOURCE(IDI_APPLICATION)); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wcex.lpszMenuName = NULL; + wcex.lpszClassName = L"ScreenCaptureforHWND"; + wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION)); + WINRT_VERIFY(RegisterClassEx(&wcex)); + + HWND hwnd = CreateWindow(L"ScreenCaptureforHWND", L"ScreenCaptureforHWND", + WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, + 800, 600, NULL, NULL, instance, NULL); + WINRT_VERIFY(hwnd); + + ShowWindow(hwnd, cmdShow); + UpdateWindow(hwnd); + + // Create combo box + HWND comboBoxHwnd = + CreateWindow(WC_COMBOBOX, L"", + CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_VSCROLL | WS_CHILD | + WS_OVERLAPPED | WS_VISIBLE, + 10, 10, 200, 200, hwnd, NULL, instance, NULL); + WINRT_VERIFY(comboBoxHwnd); + + // Populate combo box + for (auto &window : g_windows) { + SendMessage(comboBoxHwnd, CB_ADDSTRING, 0, (LPARAM)window.Title().c_str()); + } + + for (auto &monitor : g_monitors) { + SendMessage(comboBoxHwnd, CB_ADDSTRING, 0, + (LPARAM)monitor.ClassName().c_str()); + } + + // SendMessage(comboBoxHwnd, CB_SETCURSEL, 0, 0); + + // Create a DispatcherQueue for our thread + auto controller = CreateDispatcherQueueController(); + + // Initialize Composition + auto compositor = Compositor(); + auto target = CreateDesktopWindowTarget(compositor, hwnd); + auto root = compositor.CreateContainerVisual(); + root.RelativeSizeAdjustment({1.0f, 1.0f}); + target.Root(root); + + // Enqueue our capture work on the dispatcher + auto queue = controller.DispatcherQueue(); + auto success = queue.TryEnqueue([=]() -> void { g_app->Initialize(root); }); + WINRT_VERIFY(success); + + // Message pump + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return (int)msg.wParam; +} + +LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_DESTROY: + PostQuitMessage(0); + break; + case WM_COMMAND: + if (HIWORD(wParam) == CBN_SELCHANGE) { + auto index = SendMessage((HWND)lParam, CB_GETCURSEL, 0, 0); + if (index < g_windows.size() - 1) { + auto window = g_windows[index]; + g_app->StartCapture(window.Hwnd()); + } else { + auto monitor = g_monitors[index - g_windows.size()]; + g_app->StartCapture(monitor.Hmonitor()); + } + } + break; + default: + return DefWindowProc(hwnd, msg, wParam, lParam); + break; + } + + return 0; +} \ No newline at end of file diff --git a/application/remote_desk/demo/pch.cpp b/application/remote_desk/demo/pch.cpp new file mode 100644 index 0000000..1730571 --- /dev/null +++ b/application/remote_desk/demo/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" \ No newline at end of file diff --git a/application/remote_desk/demo/pch.h b/application/remote_desk/demo/pch.h new file mode 100644 index 0000000..54bdc4a --- /dev/null +++ b/application/remote_desk/demo/pch.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +// WinRT +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// STL +#include +#include + +// D3D +#include +#include +#include +#include + +// Helpers +#include "composition.interop.h" +#include "d3dHelpers.h" +#include "direct3d11.interop.h" +#include "capture.interop.h" \ No newline at end of file diff --git a/application/remote_desk/dllmain.cpp b/application/remote_desk/dllmain.cpp new file mode 100644 index 0000000..f266597 --- /dev/null +++ b/application/remote_desk/dllmain.cpp @@ -0,0 +1,19 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "pch.h" + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/application/remote_desk/error_define.h b/application/remote_desk/error_define.h new file mode 100644 index 0000000..a8cfc1e --- /dev/null +++ b/application/remote_desk/error_define.h @@ -0,0 +1,218 @@ +#ifndef ERROR_DEFINE +#define ERROR_DEFINE + + +enum AM_ERROR{ + AE_NO = 0, + AE_ERROR, + AE_UNSUPPORT, + AE_INVALID_CONTEXT, + AE_NEED_INIT, + AE_TIMEOUT, + AE_ALLOCATE_FAILED, + + //AE_CO_ + AE_CO_INITED_FAILED, + AE_CO_CREATE_FAILED, + AE_CO_GETENDPOINT_FAILED, + AE_CO_ACTIVE_DEVICE_FAILED, + AE_CO_GET_FORMAT_FAILED, + AE_CO_AUDIOCLIENT_INIT_FAILED, + AE_CO_GET_CAPTURE_FAILED, + AE_CO_CREATE_EVENT_FAILED, + AE_CO_SET_EVENT_FAILED, + AE_CO_START_FAILED, + AE_CO_ENUMENDPOINT_FAILED, + AE_CO_GET_ENDPOINT_COUNT_FAILED, + AE_CO_GET_ENDPOINT_ID_FAILED, + AE_CO_OPEN_PROPERTY_FAILED, + AE_CO_GET_VALUE_FAILED, + AE_CO_GET_BUFFER_FAILED, + AE_CO_RELEASE_BUFFER_FAILED, + AE_CO_GET_PACKET_FAILED, + AE_CO_PADDING_UNEXPECTED, + + //AE_FFMPEG_ + AE_FFMPEG_OPEN_INPUT_FAILED, + AE_FFMPEG_FIND_STREAM_FAILED, + AE_FFMPEG_FIND_DECODER_FAILED, + AE_FFMPEG_OPEN_CODEC_FAILED, + AE_FFMPEG_READ_FRAME_FAILED, + AE_FFMPEG_READ_PACKET_FAILED, + AE_FFMPEG_DECODE_FRAME_FAILED, + AE_FFMPEG_NEW_SWSCALE_FAILED, + AE_FFMPEG_FIND_ENCODER_FAILED, + AE_FFMPEG_ALLOC_CONTEXT_FAILED, + AE_FFMPEG_ENCODE_FRAME_FAILED, + AE_FFMPEG_ALLOC_FRAME_FAILED, + AE_FFMPEG_OPEN_IO_FAILED, + AE_FFMPEG_CREATE_STREAM_FAILED, + AE_FFMPEG_COPY_PARAMS_FAILED, + AE_RESAMPLE_INIT_FAILED, + AE_FFMPEG_NEW_STREAM_FAILED, + AE_FFMPEG_FIND_INPUT_FMT_FAILED, + AE_FFMPEG_WRITE_HEADER_FAILED, + AE_FFMPEG_WRITE_TRAILER_FAILED, + AE_FFMPEG_WRITE_FRAME_FAILED, + + //AE_FILTER_ + AE_FILTER_ALLOC_GRAPH_FAILED, + AE_FILTER_CREATE_FILTER_FAILED, + AE_FILTER_PARSE_PTR_FAILED, + AE_FILTER_CONFIG_FAILED, + AE_FILTER_INVALID_CTX_INDEX, + AE_FILTER_ADD_FRAME_FAILED, + + //AE_GDI_ + AE_GDI_GET_DC_FAILED, + AE_GDI_CREATE_DC_FAILED, + AE_GDI_CREATE_BMP_FAILED, + AE_GDI_BITBLT_FAILED, + AE_GDI_GET_DIBITS_FAILED, + + //AE_D3D_ + AE_D3D_LOAD_FAILED, + AE_D3D_GET_PROC_FAILED, + AE_D3D_CREATE_DEVICE_FAILED, + AE_D3D_QUERYINTERFACE_FAILED, + AE_D3D_CREATE_VERTEX_SHADER_FAILED, + AE_D3D_CREATE_INLAYOUT_FAILED, + AE_D3D_CREATE_PIXEL_SHADER_FAILED, + AE_D3D_CREATE_SAMPLERSTATE_FAILED, + + //AE_DXGI_ + AE_DXGI_GET_PROC_FAILED, + AE_DXGI_GET_ADAPTER_FAILED, + AE_DXGI_GET_FACTORY_FAILED, + AE_DXGI_FOUND_ADAPTER_FAILED, + + //AE_DUP_ + AE_DUP_ATTATCH_FAILED, + AE_DUP_QI_FAILED, + AE_DUP_GET_PARENT_FAILED, + AE_DUP_ENUM_OUTPUT_FAILED, + AE_DUP_DUPLICATE_MAX_FAILED, + AE_DUP_DUPLICATE_FAILED, + AE_DUP_RELEASE_FRAME_FAILED, + AE_DUP_ACQUIRE_FRAME_FAILED, + AE_DUP_QI_FRAME_FAILED, + AE_DUP_CREATE_TEXTURE_FAILED, + AE_DUP_QI_DXGI_FAILED, + AE_DUP_MAP_FAILED, + AE_DUP_GET_CURSORSHAPE_FAILED, + + //AE_REMUX_ + AE_REMUX_RUNNING, + AE_REMUX_NOT_EXIST, + AE_REMUX_INVALID_INOUT, + + // AE_WGC_ + AE_WGC_CREATE_CAPTURER_FAILED, + + AE_MAX +}; + +static const char *ERRORS_STR[] = { + "no error", //AE_NO + "error", //AE_ERROR + "not support for now", //AE_UNSUPPORT + "invalid context", //AE_INVALID_CONTEXT + "need init first", //AE_NEED_INIT + "operation timeout", //AE_TIMEOUT + "allocate memory failed", //AE_ALLOCATE_FAILED, + + "com init failed", //AE_CO_INITED_FAILED + "com create instance failed", //AE_CO_CREATE_FAILED + "com get endpoint failed", //AE_CO_GETENDPOINT_FAILED + "com active device failed", //AE_CO_ACTIVE_DEVICE_FAILED + "com get wave formatex failed", //AE_CO_GET_FORMAT_FAILED + "com audio client init failed", //AE_CO_AUDIOCLIENT_INIT_FAILED + "com audio get capture failed", //AE_CO_GET_CAPTURE_FAILED + "com audio create event failed", //AE_CO_CREATE_EVENT_FAILED + "com set ready event failed", //AE_CO_SET_EVENT_FAILED + "com start to record failed", //AE_CO_START_FAILED + "com enum audio endpoints failed", //AE_CO_ENUMENDPOINT_FAILED + "com get endpoints count failed", //AE_CO_GET_ENDPOINT_COUNT_FAILED + "com get endpoint id failed", //AE_CO_GET_ENDPOINT_ID_FAILED + "com open endpoint property failed", //AE_CO_OPEN_PROPERTY_FAILED + "com get property value failed", //AE_CO_GET_VALUE_FAILED + "com get buffer failed", //AE_CO_GET_BUFFER_FAILED + "com release buffer failed", //AE_CO_RELEASE_BUFFER_FAILED + "com get packet size failed", //AE_CO_GET_PACKET_FAILED + "com get padding size unexpected", //AE_CO_PADDING_UNEXPECTED + + "ffmpeg open input failed", //AE_FFMPEG_OPEN_INPUT_FAILED + "ffmpeg find stream info failed", //AE_FFMPEG_FIND_STREAM_FAILED + "ffmpeg find decoder failed", //AE_FFMPEG_FIND_DECODER_FAILED + "ffmpeg open codec failed", //AE_FFMPEG_OPEN_CODEC_FAILED + "ffmpeg read frame failed", //AE_FFMPEG_READ_FRAME_FAILED + "ffmpeg read packet failed", //AE_FFMPEG_READ_PACKET_FAILED + "ffmpeg decode frame failed", //AE_FFMPEG_DECODE_FRAME_FAILED + "ffmpeg create swscale failed", //AE_FFMPEG_NEW_SWSCALE_FAILED + + "ffmpeg find encoder failed", //AE_FFMPEG_FIND_ENCODER_FAILED + "ffmpeg alloc context failed", //AE_FFMPEG_ALLOC_CONTEXT_FAILED + "ffmpeg encode frame failed", //AE_FFMPEG_ENCODE_FRAME_FAILED + "ffmpeg alloc frame failed", //AE_FFMPEG_ALLOC_FRAME_FAILED + + "ffmpeg open io ctx failed", //AE_FFMPEG_OPEN_IO_FAILED + "ffmpeg new stream failed", //AE_FFMPEG_CREATE_STREAM_FAILED + "ffmpeg copy parameters failed", //AE_FFMPEG_COPY_PARAMS_FAILED + "resampler init failed", //AE_RESAMPLE_INIT_FAILED + "ffmpeg new out stream failed", //AE_FFMPEG_NEW_STREAM_FAILED + "ffmpeg find input format failed", //AE_FFMPEG_FIND_INPUT_FMT_FAILED + "ffmpeg write file header failed", //AE_FFMPEG_WRITE_HEADER_FAILED + "ffmpeg write file trailer failed", //AE_FFMPEG_WRITE_TRAILER_FAILED + "ffmpeg write frame failed", //AE_FFMPEG_WRITE_FRAME_FAILED + + "avfilter alloc avfilter failed", //AE_FILTER_ALLOC_GRAPH_FAILED + "avfilter create graph failed", //AE_FILTER_CREATE_FILTER_FAILED + "avfilter parse ptr failed", //AE_FILTER_PARSE_PTR_FAILED + "avfilter config graph failed", //AE_FILTER_CONFIG_FAILED + "avfilter invalid ctx index", //AE_FILTER_INVALID_CTX_INDEX + "avfilter add frame failed", //AE_FILTER_ADD_FRAME_FAILED + + "gdi get dc failed", //AE_GDI_GET_DC_FAILED + "gdi create dc failed", //AE_GDI_CREATE_DC_FAILED + "gdi create bmp failed", //AE_GDI_CREATE_BMP_FAILED + "gdi bitblt failed", //AE_GDI_BITBLT_FAILED + "gid geet dibbits failed", //AE_GDI_GET_DIBITS_FAILED + + "d3d11 library load failed", //AE_D3D_LOAD_FAILED + "d3d11 proc get failed", //AE_D3D_GET_PROC_FAILED + "d3d11 create device failed", //AE_D3D_CREATE_DEVICE_FAILED + "d3d11 query interface failed", //AE_D3D_QUERYINTERFACE_FAILED + "d3d11 create vertex shader failed",//AE_D3D_CREATE_VERTEX_SHADER_FAILED + "d3d11 create input layout failed", //AE_D3D_CREATE_INLAYOUT_FAILED + "d3d11 create pixel shader failed", //AE_D3D_CREATE_PIXEL_SHADER_FAILED + "d3d11 create sampler state failed",//AE_D3D_CREATE_SAMPLERSTATE_FAILED + + "dxgi get proc address failed", //AE_DXGI_GET_PROC_FAILED + "dxgi get adapter failed", //AE_DXGI_GET_ADAPTER_FAILED + "dxgi get factory failed", //AE_DXGI_GET_FACTORY_FAILED + "dxgi specified adapter not found", //AE_DXGI_FOUND_ADAPTER_FAILED + + "duplication attatch desktop failed", //AE_DUP_ATTATCH_FAILED + "duplication query interface failed", //AE_DUP_QI_FAILED + "duplication get parent failed", //AE_DUP_GET_PARENT_FAILED + "duplication enum ouput failed", //AE_DUP_ENUM_OUTPUT_FAILED + "duplication duplicate unavailable", //AE_DUP_DUPLICATE_MAX_FAILED + "duplication duplicate failed", //AE_DUP_DUPLICATE_FAILED + "duplication release frame failed", //AE_DUP_RELEASE_FRAME_FAILED + "duplication acquire frame failed", //AE_DUP_ACQUIRE_FRAME_FAILED + "duplication qi frame failed", //AE_DUP_QI_FRAME_FAILED + "duplication create texture failed", //AE_DUP_CREATE_TEXTURE_FAILED + "duplication dxgi qi failed", //AE_DUP_QI_DXGI_FAILED + "duplication map rects failed", //AE_DUP_MAP_FAILED + "duplication get cursor shape failed",//AE_DUP_GET_CURSORSHAPE_FAILED + + "remux is already running", //AE_REMUX_RUNNING + "remux input file do not exist", //AE_REMUX_NOT_EXIST + "remux input or output file invalid", //AE_REMUX_INVALID_INOUT +}; + +#define err2str(e) e < AE_MAX ? ERRORS_STR[e] : "unknown" + +#define AMERROR_CHECK(err) if(err != AE_NO) return err + +#endif // !ERROR_DEFINE \ No newline at end of file diff --git a/application/remote_desk/export.cpp b/application/remote_desk/export.cpp new file mode 100644 index 0000000..87ca8a7 --- /dev/null +++ b/application/remote_desk/export.cpp @@ -0,0 +1,17 @@ +#include "pch.h" + +#include + +bool wgc_is_supported() { + try { + /* no contract for IGraphicsCaptureItemInterop, verify 10.0.18362.0 */ + return winrt::Windows::Foundation::Metadata::ApiInformation:: + IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 8); + } catch (const winrt::hresult_error &) { + return false; + } catch (...) { + return false; + } +} + +am::wgc_session *wgc_create_session() { return new am::wgc_session_impl(); } \ No newline at end of file diff --git a/application/remote_desk/export.h b/application/remote_desk/export.h new file mode 100644 index 0000000..90873d1 --- /dev/null +++ b/application/remote_desk/export.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +#ifdef AMRECORDER_IMPORT +#define AMRECORDER_API extern "C" __declspec(dllimport) +#else +#define AMRECORDER_API extern "C" __declspec(dllexport) +#endif + +namespace am { + +class wgc_session { +public: + struct wgc_session_frame { + unsigned int width; + unsigned int height; + unsigned int row_pitch; + + const unsigned char *data; + }; + + class wgc_session_observer { + public: + virtual ~wgc_session_observer() {} + virtual void on_frame(const wgc_session_frame &frame) = 0; + }; + +public: + virtual void release() = 0; + + virtual int initialize(HWND hwnd) = 0; + virtual int initialize(HMONITOR hmonitor) = 0; + + virtual void register_observer(wgc_session_observer *observer) = 0; + + virtual int start() = 0; + virtual int stop() = 0; + + virtual int pause() = 0; + virtual int resume() = 0; + +protected: + virtual ~wgc_session(){}; +}; + +} // namespace am + +AMRECORDER_API bool wgc_is_supported(); +AMRECORDER_API am::wgc_session *wgc_create_session(); \ No newline at end of file diff --git a/application/remote_desk/head.h b/application/remote_desk/head.h new file mode 100644 index 0000000..32e7836 --- /dev/null +++ b/application/remote_desk/head.h @@ -0,0 +1,21 @@ +#ifndef _HEAD_H_ +#define _HEAD_H_ + +#include "record_desktop.h" + +class rd : public am::record_desktop { + public: + rd(){}; + virtual ~rd(){}; + + int init(const RECORD_DESKTOP_RECT &rect, const int fps) { return 0; }; + + int start() { return 0; }; + int pause() { return 0; }; + int resume() { return 0; }; + int stop() { return 0; }; + + void clean_up() {} +}; + +#endif \ No newline at end of file diff --git a/application/remote_desk/headers_ffmpeg.h b/application/remote_desk/headers_ffmpeg.h new file mode 100644 index 0000000..9d090ef --- /dev/null +++ b/application/remote_desk/headers_ffmpeg.h @@ -0,0 +1,22 @@ +#pragma once +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +} \ No newline at end of file diff --git a/application/remote_desk/log_helper.cpp b/application/remote_desk/log_helper.cpp new file mode 100644 index 0000000..5d1a424 --- /dev/null +++ b/application/remote_desk/log_helper.cpp @@ -0,0 +1,60 @@ +#include "log_helper.h" + +#include +#include +#include + +#include + +#define AMLOCK(A) std::lock_guard lock(A) + +#define LOG_ROLL_SIZE (1024 * 1024) + +AMLog* AMLog::_log = NULL; +std::mutex _lock; + +AMLog::AMLog(FILE* handle) : _handle(handle) { _log = this; } + +AMLog::~AMLog() { + AMLOCK(_lock); + if (_log && _handle) { + fclose(_handle); + _log = NULL; + } +} + +AMLog* AMLog::get(const char* path) { + if (_log || !path) { + return _log; + } + // DWORD size = 0; + // HANDLE file = CreateFile(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, + // FILE_ATTRIBUTE_NORMAL, NULL); if (file != INVALID_HANDLE_VALUE) { + // size = GetFileSize(file, NULL); CloseHandle(file); + // } + // if (size != INVALID_FILE_SIZE && size > LOG_ROLL_SIZE) { + // if (DeleteFileA(path) == FALSE) { + // TCHAR roll_path[MAX_PATH]; + // sprintf_s(roll_path, MAX_PATH, "%s.1", path); + // if (!MoveFileEx(path, roll_path, MOVEFILE_REPLACE_EXISTING)) { + // return NULL; + // } + // } + // } + // FILE* handle = _fsopen(path, "a+", _SH_DENYNO); + // if (!handle) { + // return NULL; + // } + // _log = new AMLog(handle); + return _log; +} + +void AMLog::printf(const char* format, ...) { + AMLOCK(_lock); + va_list args; + + va_start(args, format); + vfprintf(_handle, format, args); + va_end(args); + fflush(_handle); +} diff --git a/application/remote_desk/log_helper.h b/application/remote_desk/log_helper.h new file mode 100644 index 0000000..39999c2 --- /dev/null +++ b/application/remote_desk/log_helper.h @@ -0,0 +1,63 @@ +#ifndef AM_LOG +#define AM_LOG + +#include +#include +#include + +#include + +class AMLog { +public: + ~AMLog(); + static AMLog* get(const char* path = NULL); + void printf(const char* format, ...); + +private: + AMLog(FILE* handle); + +private: + static AMLog* _log; + FILE* _handle; +}; + + +enum AM_LOG_TYPE { + AL_TYPE_DEBUG = 0, + AL_TYPE_INFO, + AL_TYPE_WARN, + AL_TYPE_ERROR, + AL_TYPE_FATAL, +}; + +static const char *AM_LOG_STR[] = { "DEBUG", "INFO", "WARN", "ERROR", "FATAL" }; + +#define al_printf(type,format,datetime,ms,...) \ + printf("%s-%.3d [%s] [%s(%d)] " format "\n", datetime,ms,type, __FUNCTION__,__LINE__, ## __VA_ARGS__) + +#define PRINT_LINE(type, format, datetime, ms, ...) \ + printf("%s-%.3d [%s] [%s(%d)] " format "\n", datetime,ms,type, __FUNCTION__,__LINE__, ## __VA_ARGS__) + +#define al_log(type,format,...) do{ \ + struct _timeb now; \ + struct tm today; \ + char datetime_str[20]; \ + _ftime_s(&now); \ + localtime_s(&today, &now.time); \ + strftime(datetime_str, 20, "%Y-%m-%d %H:%M:%S", &today); \ + AMLog *am_log = AMLog::get(); \ + if(am_log){ \ + am_log->PRINT_LINE(AM_LOG_STR[type], format, datetime_str, now.millitm, ## __VA_ARGS__); \ + } else { \ + al_printf(AM_LOG_STR[type], format, datetime_str, now.millitm, ## __VA_ARGS__); \ + } \ +}while (0) + + +#define al_debug(format, ...) al_log(AL_TYPE_DEBUG, format, ## __VA_ARGS__) +#define al_info(format, ...) al_log(AL_TYPE_INFO, format, ## __VA_ARGS__) +#define al_warn(format, ...) al_log(AL_TYPE_WARN, format, ## __VA_ARGS__) +#define al_error(format, ...) al_log(AL_TYPE_ERROR, format, ## __VA_ARGS__) +#define al_fatal(format, ...) al_log(AL_TYPE_FATAL, format, ## __VA_ARGS__) + +#endif \ No newline at end of file diff --git a/application/remote_desk/main.cpp b/application/remote_desk/main.cpp new file mode 100644 index 0000000..24953ee --- /dev/null +++ b/application/remote_desk/main.cpp @@ -0,0 +1,17 @@ + +#define AMRECORDER_IMPORT +#include + +#include "export.h" +#include "head.h" + +int main() { + bool is_supported = wgc_is_supported(); + if (!wgc_is_supported) { + std::cout << "Not support wgc" << std::endl; + return -1; + } + + rd rd; + return 0; +} \ No newline at end of file diff --git a/application/remote_desk/pch.cpp b/application/remote_desk/pch.cpp new file mode 100644 index 0000000..1730571 --- /dev/null +++ b/application/remote_desk/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" \ No newline at end of file diff --git a/application/remote_desk/pch.h b/application/remote_desk/pch.h new file mode 100644 index 0000000..aa4c0ab --- /dev/null +++ b/application/remote_desk/pch.h @@ -0,0 +1,48 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for +// future builds. This also affects IntelliSense performance, including code +// completion and many code browsing features. However, files listed here are +// ALL re-compiled if any one of them is updated between builds. Do not add +// files here that you will be updating frequently as this negates the +// performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files + +#include +#include + +// WinRT +#include +#include +#include +#include +#include +#include + +#include + + +// STL +#include +#include + +// D3D +#include +#include +#include +#include + +// windowws + +#include + +#include "error_define.h" +#include "export.h" +#include "wgc_session_impl.h" + +#endif // PCH_H diff --git a/application/remote_desk/record_desktop.cpp b/application/remote_desk/record_desktop.cpp new file mode 100644 index 0000000..8bf0927 --- /dev/null +++ b/application/remote_desk/record_desktop.cpp @@ -0,0 +1,23 @@ +#include "record_desktop.h" + +am::record_desktop::record_desktop() +{ + _running = false; + _paused = false; + _inited = false; + + _on_data = nullptr; + _on_error = nullptr; + + _device_name = ""; + _data_type = RECORD_DESKTOP_DATA_TYPES::AT_DESKTOP_BGRA; + + _time_base = { 1,AV_TIME_BASE }; + _start_time = 0; + _pixel_fmt = AV_PIX_FMT_NONE; +} + +am::record_desktop::~record_desktop() +{ + +} diff --git a/application/remote_desk/record_desktop.h b/application/remote_desk/record_desktop.h new file mode 100644 index 0000000..efbadc7 --- /dev/null +++ b/application/remote_desk/record_desktop.h @@ -0,0 +1,80 @@ +#ifndef RECORD_DESKTOP +#define RECORD_DESKTOP + +#include "record_desktop_define.h" + +#include "headers_ffmpeg.h" + +#include +#include +#include +#include + +namespace am { + typedef std::function cb_desktop_data; + typedef std::function cb_desktop_error; + + class record_desktop + { + public: + record_desktop(); + virtual ~record_desktop(); + + virtual int init( + const RECORD_DESKTOP_RECT &rect, + const int fps + ) = 0; + + virtual int start() = 0; + virtual int pause() = 0; + virtual int resume() = 0; + virtual int stop() = 0; + + inline const AVRational & get_time_base() { return _time_base; } + + inline int64_t get_start_time() { return _start_time; } + + inline AVPixelFormat get_pixel_fmt() { return _pixel_fmt; } + + public: + inline bool is_recording() { return _running; } + inline const std::string & get_device_name() { return _device_name; } + inline const RECORD_DESKTOP_DATA_TYPES get_data_type() { return _data_type; } + inline void registe_cb( + cb_desktop_data on_data, + cb_desktop_error on_error) { + _on_data = on_data; + _on_error = on_error; + } + inline const RECORD_DESKTOP_RECT & get_rect() { return _rect; } + + inline const int get_frame_rate() { return _fps; } + + protected: + virtual void clean_up() = 0; + + protected: + std::atomic_bool _running; + std::atomic_bool _paused; + std::atomic_bool _inited; + + std::thread _thread; + + std::string _device_name; + + RECORD_DESKTOP_RECT _rect; + RECORD_DESKTOP_DATA_TYPES _data_type; + + int _fps; + + cb_desktop_data _on_data; + cb_desktop_error _on_error; + + AVRational _time_base; + int64_t _start_time; + AVPixelFormat _pixel_fmt; + }; +} + + +#endif \ No newline at end of file diff --git a/application/remote_desk/record_desktop_define.h b/application/remote_desk/record_desktop_define.h new file mode 100644 index 0000000..797ca79 --- /dev/null +++ b/application/remote_desk/record_desktop_define.h @@ -0,0 +1,45 @@ +#ifndef RECORD_DESKTOP_DEFINE +#define RECORD_DESKTOP_DEFINE + +/* +* Record typee +* +*/ +typedef enum { + DT_DESKTOP_NO = 0, + DT_DESKTOP_FFMPEG_GDI, + DT_DESKTOP_FFMPEG_DSHOW, + DT_DESKTOP_WIN_GDI, + DT_DESKTOP_WIN_DUPLICATION, + DT_DESKTOP_WIN_WGC, + DT_DESKTOP_WIN_MAG +}RECORD_DESKTOP_TYPES; + +/* +* Record desktop data type +* +*/ + +typedef enum { + AT_DESKTOP_NO = 0, + AT_DESKTOP_RGBA, + AT_DESKTOP_BGRA +}RECORD_DESKTOP_DATA_TYPES; + +/** +* Record desktop rect +* +*/ + +typedef struct { + int left; + int top; + int right; + int bottom; +}RECORD_DESKTOP_RECT; + + + + + +#endif diff --git a/application/remote_desk/record_desktop_wgc.cpp b/application/remote_desk/record_desktop_wgc.cpp new file mode 100644 index 0000000..0fda4c1 --- /dev/null +++ b/application/remote_desk/record_desktop_wgc.cpp @@ -0,0 +1,144 @@ +#include "record_desktop_wgc.h" + +#include "error_define.h" +#include "log_helper.h" +#include "system_error.h" +#include "utils_string.h" + +BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, HDC hdc, LPRECT lprc, + LPARAM data) { + MONITORINFOEX info_ex; + info_ex.cbSize = sizeof(MONITORINFOEX); + + GetMonitorInfo(hmonitor, &info_ex); + + if (info_ex.dwFlags == DISPLAY_DEVICE_MIRRORING_DRIVER) return true; + + if (info_ex.dwFlags & MONITORINFOF_PRIMARY) { + *(HMONITOR *)data = hmonitor; + } + + return true; +} + +HMONITOR GetPrimaryMonitor() { + HMONITOR hmonitor = nullptr; + + ::EnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&hmonitor); + + return hmonitor; +} + +namespace am { + +record_desktop_wgc::record_desktop_wgc() {} + +record_desktop_wgc::~record_desktop_wgc() { + stop(); + clean_up(); +} + +int record_desktop_wgc::init(const RECORD_DESKTOP_RECT &rect, const int fps) { + int error = AE_NO; + if (_inited == true) return error; + + _fps = fps; + _rect = rect; + _start_time = av_gettime_relative(); + _time_base = {1, AV_TIME_BASE}; + _pixel_fmt = AV_PIX_FMT_BGRA; + + do { + if (!module_.is_supported()) { + error = AE_UNSUPPORT; + break; + } + + session_ = module_.create_session(); + if (!session_) { + error = AE_WGC_CREATE_CAPTURER_FAILED; + break; + } + + session_->register_observer(this); + + error = session_->initialize(GetPrimaryMonitor()); + + _inited = true; + } while (0); + + if (error != AE_NO) { + al_debug("%s,last error:%s", err2str(error), + system_error::error2str(GetLastError()).c_str()); + } + + return error; +} + +int record_desktop_wgc::start() { + if (_running == true) { + al_warn("record desktop duplication is already running"); + return AE_NO; + } + + if (_inited == false) { + return AE_NEED_INIT; + } + + _running = true; + session_->start(); + + return AE_NO; +} + +int record_desktop_wgc::pause() { + _paused = true; + if (session_) session_->pause(); + return AE_NO; +} + +int record_desktop_wgc::resume() { + _paused = false; + if (session_) session_->resume(); + return AE_NO; +} + +int record_desktop_wgc::stop() { + _running = false; + + if (session_) session_->stop(); + + return AE_NO; +} + +void record_desktop_wgc::on_frame(const wgc_session::wgc_session_frame &frame) { + al_debug("wgc on frame"); + AVFrame *av_frame = av_frame_alloc(); + + av_frame->pts = av_gettime_relative(); + av_frame->pkt_dts = av_frame->pts; + // av_frame->pkt_pts = av_frame->pts; + + av_frame->width = frame.width; + av_frame->height = frame.height; + av_frame->format = AV_PIX_FMT_BGRA; + av_frame->pict_type = AV_PICTURE_TYPE_NONE; + av_frame->pkt_size = frame.width * frame.height * 4; + + av_image_fill_arrays(av_frame->data, av_frame->linesize, frame.data, + AV_PIX_FMT_BGRA, frame.width, frame.height, 1); + + if (_on_data) _on_data(av_frame); + + av_frame_free(&av_frame); +} + +void record_desktop_wgc::clean_up() { + _inited = false; + + if (session_) session_->release(); + + session_ = nullptr; +} + +} // namespace am \ No newline at end of file diff --git a/application/remote_desk/record_desktop_wgc.h b/application/remote_desk/record_desktop_wgc.h new file mode 100644 index 0000000..4c5adc1 --- /dev/null +++ b/application/remote_desk/record_desktop_wgc.h @@ -0,0 +1,65 @@ +#pragma once + +#include + +#include "export.h" +#include "record_desktop.h" + +namespace am { +class record_desktop_wgc : public record_desktop, + public wgc_session::wgc_session_observer { + class wgc_session_module { + using func_type_is_supported = bool (*)(); + using func_type_create_session = wgc_session *(*)(); + + public: + wgc_session_module() { + module_ = ::LoadLibraryA("WGC.dll"); + if (module_) { + func_is_supported_ = (func_type_is_supported)::GetProcAddress( + module_, "wgc_is_supported"); + func_create_session_ = (func_type_create_session)::GetProcAddress( + module_, "wgc_create_session"); + } + } + ~wgc_session_module() { + if (module_) ::FreeModule(module_); + } + + bool is_supported() const { + return func_create_session_ && func_is_supported_(); + } + + wgc_session *create_session() const { + if (!func_create_session_) return nullptr; + + return func_create_session_(); + } + + private: + HMODULE module_ = nullptr; + func_type_is_supported func_is_supported_ = nullptr; + func_type_create_session func_create_session_ = nullptr; + }; + + public: + record_desktop_wgc(); + ~record_desktop_wgc(); + + int init(const RECORD_DESKTOP_RECT &rect, const int fps) override; + + int start() override; + int pause() override; + int resume() override; + int stop() override; + + void on_frame(const wgc_session::wgc_session_frame &frame) override; + + protected: + void clean_up() override; + + private: + wgc_session *session_ = nullptr; + wgc_session_module module_; +}; +} // namespace am \ No newline at end of file diff --git a/application/remote_desk/remote_desk.cpp b/application/remote_desk/remote_desk.cpp.bak similarity index 100% rename from application/remote_desk/remote_desk.cpp rename to application/remote_desk/remote_desk.cpp.bak diff --git a/application/remote_desk/system_error.cpp b/application/remote_desk/system_error.cpp new file mode 100644 index 0000000..001bdca --- /dev/null +++ b/application/remote_desk/system_error.cpp @@ -0,0 +1,39 @@ +#include "system_error.h" + +#include + +namespace am { + +const std::string& system_error::error2str(unsigned long error) { + // DWORD system_locale = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL); + + // HLOCAL local_buf = nullptr; + + // BOOL ret = FormatMessage( + // FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | + // FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, error, system_locale,(PSTR) + // &local_buf, 0, NULL); + + // if (!ret) { + // HMODULE hnetmsg = LoadLibraryEx("netmsg.dll", NULL, + // DONT_RESOLVE_DLL_REFERENCES); if (hnetmsg != nullptr) { + // ret = FormatMessage( FORMAT_MESSAGE_FROM_HMODULE | + // FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, + // hnetmsg, error, system_locale, (PSTR)&local_buf, 0, NULL); + + // FreeLibrary(hnetmsg); + // } + // } + + // std::string error_str; + + // if (ret) { + // error_str = (LPCTSTR)LocalLock(local_buf); + // LocalFree(local_buf); + // } + + // return error_str; + return ""; +} + +} // namespace am \ No newline at end of file diff --git a/application/remote_desk/system_error.h b/application/remote_desk/system_error.h new file mode 100644 index 0000000..cb8120f --- /dev/null +++ b/application/remote_desk/system_error.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace am { + +class system_error { +public: + static const std::string& error2str(unsigned long error); +}; + +} diff --git a/application/remote_desk/utils_string.cpp b/application/remote_desk/utils_string.cpp new file mode 100644 index 0000000..90b921a --- /dev/null +++ b/application/remote_desk/utils_string.cpp @@ -0,0 +1,66 @@ +#include "utils_string.h" + +#include +#ifdef WIN32 + +#include + +#endif + +namespace am { + +std::wstring utils_string::ascii_unicode(const std::string &str) { + int unicodeLen = MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, nullptr, 0); + + wchar_t *pUnicode = (wchar_t *)malloc(sizeof(wchar_t) * unicodeLen); + + MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, pUnicode, unicodeLen); + + std::wstring ret_str = pUnicode; + + free(pUnicode); + + return ret_str; +} + +std::string utils_string::unicode_ascii(const std::wstring &wstr) { + int ansiiLen = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, nullptr, 0, + nullptr, nullptr); + char *pAssii = (char *)malloc(sizeof(char) * ansiiLen); + WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, pAssii, ansiiLen, nullptr, + nullptr); + std::string ret_str = pAssii; + free(pAssii); + return ret_str; +} + +std::string utils_string::ascii_utf8(const std::string &str) { + return unicode_utf8(ascii_unicode(str)); +} + +std::string utils_string::utf8_ascii(const std::string &utf8) { + return unicode_ascii(utf8_unicode(utf8)); +} + +std::string utils_string::unicode_utf8(const std::wstring &wstr) { + int ansiiLen = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, + nullptr, nullptr); + char *pAssii = (char *)malloc(sizeof(char) * ansiiLen); + WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, pAssii, ansiiLen, nullptr, + nullptr); + std::string ret_str = pAssii; + free(pAssii); + return ret_str; +} + +std::wstring utils_string::utf8_unicode(const std::string &utf8) { + int unicodeLen = + MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, nullptr, 0); + wchar_t *pUnicode = (wchar_t *)malloc(sizeof(wchar_t) * unicodeLen); + MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, pUnicode, unicodeLen); + std::wstring ret_str = pUnicode; + free(pUnicode); + return ret_str; +} + +} // namespace am \ No newline at end of file diff --git a/application/remote_desk/utils_string.h b/application/remote_desk/utils_string.h new file mode 100644 index 0000000..8c5dc42 --- /dev/null +++ b/application/remote_desk/utils_string.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace am { + + class utils_string + { + public: + static std::wstring ascii_unicode(const std::string & str); + + static std::string unicode_ascii(const std::wstring &wstr); + + static std::string ascii_utf8(const std::string & str); + + static std::string utf8_ascii(const std::string &utf8); + + static std::string unicode_utf8(const std::wstring& wstr); + + static std::wstring utf8_unicode(const std::string &utf8); + }; + +} \ No newline at end of file diff --git a/application/remote_desk/wgc_session_impl.cpp b/application/remote_desk/wgc_session_impl.cpp new file mode 100644 index 0000000..9a2916d --- /dev/null +++ b/application/remote_desk/wgc_session_impl.cpp @@ -0,0 +1,425 @@ +#include "pch.h" + +#include +#include + +#define CHECK_INIT \ + if (!is_initialized_) \ + return AM_ERROR::AE_NEED_INIT + +#define CHECK_CLOSED \ + if (cleaned_.load() == true) { \ + throw winrt::hresult_error(RO_E_CLOSED); \ + } + +extern "C" { +HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice( + ::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice); +} + +namespace am { + +wgc_session_impl::wgc_session_impl() {} + +wgc_session_impl::~wgc_session_impl() { + stop(); + cleanup(); +} + +void wgc_session_impl::release() { delete this; } + +int wgc_session_impl::initialize(HWND hwnd) { + std::lock_guard locker(lock_); + + target_.hwnd = hwnd; + target_.is_window = true; + return initialize(); +} + +int wgc_session_impl::initialize(HMONITOR hmonitor) { + std::lock_guard locker(lock_); + + target_.hmonitor = hmonitor; + target_.is_window = false; + return initialize(); +} + +void wgc_session_impl::register_observer(wgc_session_observer *observer) { + std::lock_guard locker(lock_); + observer_ = observer; +} + +int wgc_session_impl::start() { + std::lock_guard locker(lock_); + + if (is_running_) + return AM_ERROR::AE_NO; + + int error = AM_ERROR::AE_WGC_CREATE_CAPTURER_FAILED; + + CHECK_INIT; + try { + if (!capture_session_) { + auto current_size = capture_item_.Size(); + capture_framepool_ = + winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool:: + CreateFreeThreaded(d3d11_direct_device_, + winrt::Windows::Graphics::DirectX:: + DirectXPixelFormat::B8G8R8A8UIntNormalized, + 2, current_size); + capture_session_ = capture_framepool_.CreateCaptureSession(capture_item_); + capture_frame_size_ = current_size; + capture_framepool_trigger_ = capture_framepool_.FrameArrived( + winrt::auto_revoke, {this, &wgc_session_impl::on_frame}); + capture_close_trigger_ = capture_item_.Closed( + winrt::auto_revoke, {this, &wgc_session_impl::on_closed}); + } + + if (!capture_framepool_) + throw std::exception(); + + is_running_ = true; + + // we do not need to crate a thread to enter a message loop coz we use + // CreateFreeThreaded instead of Create to create a capture frame pool, + // we need to test the performance later + // loop_ = std::thread(std::bind(&wgc_session_impl::message_func, this)); + + capture_session_.StartCapture(); + + error = AM_ERROR::AE_NO; + } catch (winrt::hresult_error) { + return AM_ERROR::AE_WGC_CREATE_CAPTURER_FAILED; + } catch (...) { + return AM_ERROR::AE_WGC_CREATE_CAPTURER_FAILED; + } + + return error; +} + +int wgc_session_impl::stop() { + std::lock_guard locker(lock_); + + CHECK_INIT; + + is_running_ = false; + + if (loop_.joinable()) + loop_.join(); + + if (capture_framepool_trigger_) + capture_framepool_trigger_.revoke(); + + if (capture_session_) { + capture_session_.Close(); + capture_session_ = nullptr; + } + + return AM_ERROR::AE_NO; +} + +int wgc_session_impl::pause() { + std::lock_guard locker(lock_); + + CHECK_INIT; + return AM_ERROR::AE_NO; +} + +int wgc_session_impl::resume() { + std::lock_guard locker(lock_); + + CHECK_INIT; + return AM_ERROR::AE_NO; +} + +auto wgc_session_impl::create_d3d11_device() { + auto create_d3d_device = [](D3D_DRIVER_TYPE const type, + winrt::com_ptr &device) { + WINRT_ASSERT(!device); + + UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + + //#ifdef _DEBUG + // flags |= D3D11_CREATE_DEVICE_DEBUG; + //#endif + + return ::D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0, + D3D11_SDK_VERSION, device.put(), nullptr, + nullptr); + }; + auto create_d3d_device_wrapper = [&create_d3d_device]() { + winrt::com_ptr device; + HRESULT hr = create_d3d_device(D3D_DRIVER_TYPE_HARDWARE, device); + + if (DXGI_ERROR_UNSUPPORTED == hr) { + hr = create_d3d_device(D3D_DRIVER_TYPE_WARP, device); + } + + winrt::check_hresult(hr); + return device; + }; + + auto d3d_device = create_d3d_device_wrapper(); + auto dxgi_device = d3d_device.as(); + + winrt::com_ptr<::IInspectable> d3d11_device; + winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice( + dxgi_device.get(), d3d11_device.put())); + return d3d11_device + .as(); +} + +auto wgc_session_impl::create_capture_item(HWND hwnd) { + auto activation_factory = winrt::get_activation_factory< + winrt::Windows::Graphics::Capture::GraphicsCaptureItem>(); + auto interop_factory = activation_factory.as(); + winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr}; + interop_factory->CreateForWindow( + hwnd, + winrt::guid_of(), + reinterpret_cast(winrt::put_abi(item))); + return item; +} + +auto wgc_session_impl::create_capture_item(HMONITOR hmonitor) { + auto activation_factory = winrt::get_activation_factory< + winrt::Windows::Graphics::Capture::GraphicsCaptureItem>(); + auto interop_factory = activation_factory.as(); + winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr}; + interop_factory->CreateForMonitor( + hmonitor, + winrt::guid_of(), + reinterpret_cast(winrt::put_abi(item))); + return item; +} + +HRESULT wgc_session_impl::create_mapped_texture( + winrt::com_ptr src_texture, unsigned int width, + unsigned int height) { + + D3D11_TEXTURE2D_DESC src_desc; + src_texture->GetDesc(&src_desc); + D3D11_TEXTURE2D_DESC map_desc; + map_desc.Width = width == 0 ? src_desc.Width : width; + map_desc.Height = height == 0 ? src_desc.Height : height; + map_desc.MipLevels = src_desc.MipLevels; + map_desc.ArraySize = src_desc.ArraySize; + map_desc.Format = src_desc.Format; + map_desc.SampleDesc = src_desc.SampleDesc; + map_desc.Usage = D3D11_USAGE_STAGING; + map_desc.BindFlags = 0; + map_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + map_desc.MiscFlags = 0; + + auto d3dDevice = get_dxgi_interface(d3d11_direct_device_); + + return d3dDevice->CreateTexture2D(&map_desc, nullptr, + d3d11_texture_mapped_.put()); +} + +void wgc_session_impl::on_frame( + winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender, + winrt::Windows::Foundation::IInspectable const &args) { + std::lock_guard locker(lock_); + + auto is_new_size = false; + + { + auto frame = sender.TryGetNextFrame(); + auto frame_size = frame.ContentSize(); + + if (frame_size.Width != capture_frame_size_.Width || + frame_size.Height != capture_frame_size_.Height) { + // The thing we have been capturing has changed size. + // We need to resize our swap chain first, then blit the pixels. + // After we do that, retire the frame and then recreate our frame pool. + is_new_size = true; + capture_frame_size_ = frame_size; + } + + // copy to mapped texture + { + auto frame_captured = + get_dxgi_interface(frame.Surface()); + + if (!d3d11_texture_mapped_ || is_new_size) + create_mapped_texture(frame_captured); + + d3d11_device_context_->CopyResource(d3d11_texture_mapped_.get(), + frame_captured.get()); + + D3D11_MAPPED_SUBRESOURCE map_result; + HRESULT hr = d3d11_device_context_->Map( + d3d11_texture_mapped_.get(), 0, D3D11_MAP_READ, + 0 /*coz we use CreateFreeThreaded, so we cant use flags + D3D11_MAP_FLAG_DO_NOT_WAIT*/ + , + &map_result); + if (FAILED(hr)) { + OutputDebugStringW( + (L"map resource failed: " + std::to_wstring(hr)).c_str()); + } + + // copy data from map_result.pData + if (map_result.pData && observer_) { + observer_->on_frame(wgc_session_frame{ + static_cast(frame_size.Width), + static_cast(frame_size.Height), map_result.RowPitch, + const_cast( + (unsigned char *)map_result.pData)}); + } +#if 0 + if (map_result.pData) { + static unsigned char *buffer = nullptr; + if (buffer && is_new_size) + delete[] buffer; + + if (!buffer) + buffer = new unsigned char[frame_size.Width * frame_size.Height * 4]; + + int dstRowPitch = frame_size.Width * 4; + for (int h = 0; h < frame_size.Height; h++) { + memcpy_s(buffer + h * dstRowPitch, dstRowPitch, + (BYTE *)map_result.pData + h * map_result.RowPitch, + min(map_result.RowPitch, dstRowPitch)); + } + + BITMAPINFOHEADER bi; + + bi.biSize = sizeof(BITMAPINFOHEADER); + bi.biWidth = frame_size.Width; + bi.biHeight = frame_size.Height * (-1); + bi.biPlanes = 1; + bi.biBitCount = 32; // should get from system color bits + bi.biCompression = BI_RGB; + bi.biSizeImage = 0; + bi.biXPelsPerMeter = 0; + bi.biYPelsPerMeter = 0; + bi.biClrUsed = 0; + bi.biClrImportant = 0; + + BITMAPFILEHEADER bf; + bf.bfType = 0x4d42; + bf.bfReserved1 = 0; + bf.bfReserved2 = 0; + bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); + bf.bfSize = bf.bfOffBits + frame_size.Width * frame_size.Height * 4; + + FILE *fp = nullptr; + + fopen_s(&fp, ".\\save.bmp", "wb+"); + + fwrite(&bf, 1, sizeof(bf), fp); + fwrite(&bi, 1, sizeof(bi), fp); + fwrite(buffer, 1, frame_size.Width * frame_size.Height * 4, fp); + + fflush(fp); + fclose(fp); + } +#endif + + d3d11_device_context_->Unmap(d3d11_texture_mapped_.get(), 0); + } + } + + if (is_new_size) { + capture_framepool_.Recreate(d3d11_direct_device_, + winrt::Windows::Graphics::DirectX:: + DirectXPixelFormat::B8G8R8A8UIntNormalized, + 2, capture_frame_size_); + } +} + +void wgc_session_impl::on_closed( + winrt::Windows::Graphics::Capture::GraphicsCaptureItem const &, + winrt::Windows::Foundation::IInspectable const &) { + OutputDebugStringW(L"wgc_session_impl::on_closed"); +} + +int wgc_session_impl::initialize() { + if (is_initialized_) + return AM_ERROR::AE_NO; + + if (!(d3d11_direct_device_ = create_d3d11_device())) + return AM_ERROR::AE_D3D_CREATE_DEVICE_FAILED; + + try { + if (target_.is_window) + capture_item_ = create_capture_item(target_.hwnd); + else + capture_item_ = create_capture_item(target_.hmonitor); + + // Set up + auto d3d11_device = get_dxgi_interface(d3d11_direct_device_); + d3d11_device->GetImmediateContext(d3d11_device_context_.put()); + + } catch (winrt::hresult_error) { + return AM_ERROR::AE_WGC_CREATE_CAPTURER_FAILED; + } catch (...) { + return AM_ERROR::AE_WGC_CREATE_CAPTURER_FAILED; + } + + is_initialized_ = true; + + return AM_ERROR::AE_NO; +} + +void wgc_session_impl::cleanup() { + std::lock_guard locker(lock_); + + auto expected = false; + if (cleaned_.compare_exchange_strong(expected, true)) { + capture_close_trigger_.revoke(); + capture_framepool_trigger_.revoke(); + + if (capture_framepool_) + capture_framepool_.Close(); + + if (capture_session_) + capture_session_.Close(); + + capture_framepool_ = nullptr; + capture_session_ = nullptr; + capture_item_ = nullptr; + + is_initialized_ = false; + } +} + +LRESULT CALLBACK WindowProc(HWND window, UINT message, WPARAM w_param, + LPARAM l_param) { + return DefWindowProc(window, message, w_param, l_param); +} + +void wgc_session_impl::message_func() { + const std::wstring kClassName = L"am_fake_window"; + + WNDCLASS wc = {}; + + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = DefWindowProc; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = reinterpret_cast(COLOR_WINDOW); + wc.lpszClassName = kClassName.c_str(); + + if (!::RegisterClassW(&wc)) + return; + + hwnd_ = ::CreateWindowW(kClassName.c_str(), nullptr, WS_OVERLAPPEDWINDOW, 0, + 0, 0, 0, nullptr, nullptr, nullptr, nullptr); + MSG msg; + while (is_running_) { + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (!is_running_) + break; + TranslateMessage(&msg); + DispatchMessage(&msg); + } + Sleep(10); + } + + ::CloseWindow(hwnd_); + ::DestroyWindow(hwnd_); +} + +} // namespace am \ No newline at end of file diff --git a/application/remote_desk/wgc_session_impl.h b/application/remote_desk/wgc_session_impl.h new file mode 100644 index 0000000..7b912c0 --- /dev/null +++ b/application/remote_desk/wgc_session_impl.h @@ -0,0 +1,104 @@ +#pragma once + +#include +#include + +namespace am { +class wgc_session_impl : public wgc_session { + struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")) + IDirect3DDxgiInterfaceAccess : ::IUnknown { + virtual HRESULT __stdcall GetInterface(GUID const &id, void **object) = 0; + }; + + struct { + union { + HWND hwnd; + HMONITOR hmonitor; + }; + bool is_window; + } target_{0}; + +public: + wgc_session_impl(); + ~wgc_session_impl() override; + +public: + void release() override; + + int initialize(HWND hwnd) override; + int initialize(HMONITOR hmonitor) override; + + void register_observer(wgc_session_observer *observer) override; + + int start() override; + int stop() override; + + int pause() override; + int resume() override; + +private: + auto create_d3d11_device(); + auto create_capture_item(HWND hwnd); + auto create_capture_item(HMONITOR hmonitor); + template + auto + get_dxgi_interface(winrt::Windows::Foundation::IInspectable const &object); + HRESULT create_mapped_texture(winrt::com_ptr src_texture, + unsigned int width = 0, + unsigned int height = 0); + void + on_frame(winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const + &sender, + winrt::Windows::Foundation::IInspectable const &args); + void on_closed(winrt::Windows::Graphics::Capture::GraphicsCaptureItem const &, + winrt::Windows::Foundation::IInspectable const &); + + int initialize(); + void cleanup(); + + void message_func(); + +private: + std::mutex lock_; + bool is_initialized_ = false; + bool is_running_ = false; + bool is_paused_ = false; + + wgc_session_observer *observer_ = nullptr; + + // wgc + winrt::Windows::Graphics::Capture::GraphicsCaptureItem capture_item_{nullptr}; + winrt::Windows::Graphics::Capture::GraphicsCaptureSession capture_session_{ + nullptr}; + winrt::Windows::Graphics::SizeInt32 capture_frame_size_; + + winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice + d3d11_direct_device_{nullptr}; + winrt::com_ptr d3d11_device_context_{nullptr}; + winrt::com_ptr d3d11_texture_mapped_{nullptr}; + + std::atomic cleaned_ = false; + winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool + capture_framepool_{nullptr}; + winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool:: + FrameArrived_revoker capture_framepool_trigger_; + winrt::Windows::Graphics::Capture::GraphicsCaptureItem::Closed_revoker + capture_close_trigger_; + + // message loop + std::thread loop_; + HWND hwnd_ = nullptr; +}; + +template +inline auto wgc_session_impl::get_dxgi_interface( + winrt::Windows::Foundation::IInspectable const &object) { + auto access = object.as(); + winrt::com_ptr result; + winrt::check_hresult( + access->GetInterface(winrt::guid_of(), result.put_void())); + return result; +} + + +} // namespace am \ No newline at end of file diff --git a/application/remote_desk/xmake.lua b/application/remote_desk/xmake.lua index c6ab75c..7cba4c5 100644 --- a/application/remote_desk/xmake.lua +++ b/application/remote_desk/xmake.lua @@ -7,12 +7,13 @@ set_languages("c++17") add_rules("mode.release", "mode.debug") -add_requires("spdlog 1.11.0", "ffmpeg", {system = false}) +add_requires("spdlog 1.11.0", "ffmpeg 5.1.2", {system = false}) +add_defines("UNICODE") if is_os("windows") then - add_ldflags("/SUBSYSTEM:CONSOLE") - add_links("Shell32") - add_requires("vcpkg::sdl2 2.26.4") + -- add_ldflags("/SUBSYSTEM:CONSOLE") + add_links("Shell32", "windowsapp", "dwmapi", "User32", "kernel32") + -- add_requires("vcpkg::sdl2") elseif is_os("linux") then add_links("pthread") set_config("cxxflags", "-fPIC") @@ -32,8 +33,8 @@ target("remote_desk") set_kind("binary") add_deps("projectx") add_packages("log") - add_packages("vcpkg::sdl2", "ffmpeg") + add_packages("ffmpeg") add_links("avfilter", "avdevice", "avformat", "avcodec", "swscale", "swresample", "avutil") add_files("*.cpp") add_includedirs("../../src/interface") - add_links("SDL2main", "SDL2-static") + -- add_links("SDL2main", "SDL2-static")