diff --git a/Assets/uDesktopDuplication/Plugins/uDesktopDuplication.dll b/Assets/uDesktopDuplication/Plugins/uDesktopDuplication.dll index b7bc5d6..5964779 100644 Binary files a/Assets/uDesktopDuplication/Plugins/uDesktopDuplication.dll and b/Assets/uDesktopDuplication/Plugins/uDesktopDuplication.dll differ diff --git a/Assets/uDesktopDuplication/Scripts/Cursor.cs b/Assets/uDesktopDuplication/Scripts/Cursor.cs index 3a030b4..b7d1dc7 100644 --- a/Assets/uDesktopDuplication/Scripts/Cursor.cs +++ b/Assets/uDesktopDuplication/Scripts/Cursor.cs @@ -47,7 +47,7 @@ public class Cursor : MonoBehaviour void Update() { - if (monitor.isPointerVisible) { + if (monitor.isCursorVisible) { UpdatePosition(); UpdateTexture(); UpdateMaterial(); @@ -76,7 +76,7 @@ public class Cursor : MonoBehaviour pointerTextures_.Add(scale, texture); } var pointerTexture = pointerTextures_[scale]; - monitor.UpdatePointerTexture(pointerTexture.GetNativeTexturePtr()); + monitor.UpdateCursorTexture(pointerTexture.GetNativeTexturePtr()); cursorMaterial_.SetTexture("_MainTex", pointerTexture); } diff --git a/Assets/uDesktopDuplication/Scripts/Lib.cs b/Assets/uDesktopDuplication/Scripts/Lib.cs index aff5155..2445604 100644 --- a/Assets/uDesktopDuplication/Scripts/Lib.cs +++ b/Assets/uDesktopDuplication/Scripts/Lib.cs @@ -5,7 +5,7 @@ using System.Runtime.InteropServices; namespace uDesktopDuplication { -public enum PointerShapeType +public enum CursorShapeType { MonoChrome = 1, Color = 2, @@ -29,27 +29,23 @@ public static class Lib [DllImport("uDesktopDuplication")] public static extern bool IsPrimary(int id); [DllImport("uDesktopDuplication")] - public static extern bool IsPointerVisible(int id); + public static extern bool IsCursorVisible(int id); [DllImport("uDesktopDuplication")] - public static extern int GetPointerX(int id); + public static extern int GetCursorX(int id); [DllImport("uDesktopDuplication")] - public static extern int GetPointerY(int id); + public static extern int GetCursorY(int id); [DllImport("uDesktopDuplication")] - public static extern int GetPointerShapeWidth(int id); + public static extern int GetCursorShapeWidth(int id); [DllImport("uDesktopDuplication")] - public static extern int GetPointerShapeHeight(int id); + public static extern int GetCursorShapeHeight(int id); [DllImport("uDesktopDuplication")] - public static extern int GetPointerShapePitch(int id); + public static extern int GetCursorShapePitch(int id); [DllImport("uDesktopDuplication")] - public static extern PointerShapeType GetPointerShapeType(int id); + public static extern CursorShapeType GetCursorShapeType(int id); [DllImport("uDesktopDuplication")] - public static extern void UpdatePointerTexture(int id, System.IntPtr ptr); + public static extern void UpdateCursorTexture(int id, System.IntPtr ptr); [DllImport("uDesktopDuplication")] public static extern int SetTexturePtr(int id, IntPtr ptr); - [DllImport("uDesktopDuplication")] - public static extern int GetErrorCode(); - [DllImport("uDesktopDuplication")] - public static extern void GetErrorMessage(StringBuilder buf, int len); public static string GetName(int id) { @@ -57,13 +53,6 @@ public static class Lib GetName(id, buf, buf.Capacity); return buf.ToString(); } - - public static string GetErrorMessage() - { - var buf = new StringBuilder(64); - GetErrorMessage(buf, buf.Capacity); - return buf.ToString(); - } } } \ No newline at end of file diff --git a/Assets/uDesktopDuplication/Scripts/Manager.cs b/Assets/uDesktopDuplication/Scripts/Manager.cs index 2db896e..0220dde 100644 --- a/Assets/uDesktopDuplication/Scripts/Manager.cs +++ b/Assets/uDesktopDuplication/Scripts/Manager.cs @@ -40,9 +40,6 @@ public class Manager : MonoBehaviour [SerializeField, Tooltip("Set Desktop Duplication API timeout (milliseconds).")] int timeout = 0; - [SerializeField, Tooltip("Output logs given by the plugin.")] - bool outputDebugLog = false; - private Coroutine renderCoroutine_ = null; void Awake() @@ -81,10 +78,6 @@ public class Manager : MonoBehaviour } monitor.shouldBeUpdated = false; } - if (outputDebugLog && Lib.GetErrorCode() != 0) - { - Debug.Log(Lib.GetErrorMessage()); - } } } } diff --git a/Assets/uDesktopDuplication/Scripts/Monitor.cs b/Assets/uDesktopDuplication/Scripts/Monitor.cs index bb7c3ae..f886f6b 100644 --- a/Assets/uDesktopDuplication/Scripts/Monitor.cs +++ b/Assets/uDesktopDuplication/Scripts/Monitor.cs @@ -51,34 +51,34 @@ public class Monitor get { return height > width; } } - public bool isPointerVisible + public bool isCursorVisible { - get { return Lib.IsPointerVisible(id); } + get { return Lib.IsCursorVisible(id); } } public int pointerX { - get { return Lib.GetPointerX(id); } + get { return Lib.GetCursorX(id); } } public int pointerY { - get { return Lib.GetPointerY(id); } + get { return Lib.GetCursorY(id); } } public int pointerShapeWidth { - get { return Lib.GetPointerShapeWidth(id); } + get { return Lib.GetCursorShapeWidth(id); } } public int pointerShapeHeight { - get { return Lib.GetPointerShapeHeight(id); } + get { return Lib.GetCursorShapeHeight(id); } } - public PointerShapeType pointerShapeType + public CursorShapeType pointerShapeType { - get { return Lib.GetPointerShapeType(id); } + get { return Lib.GetCursorShapeType(id); } } public bool shouldBeUpdated @@ -107,9 +107,9 @@ public class Monitor GL.IssuePluginEvent(Lib.GetRenderEventFunc(), id); } - public void UpdatePointerTexture(System.IntPtr ptr) + public void UpdateCursorTexture(System.IntPtr ptr) { - Lib.UpdatePointerTexture(id, ptr); + Lib.UpdateCursorTexture(id, ptr); } } diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Common.h b/Plugins/uDesktopDuplication/uDesktopDuplication/Common.h new file mode 100644 index 0000000..ade96bf --- /dev/null +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/Common.h @@ -0,0 +1,8 @@ +#include +#include "IUnityInterface.h" + +class MonitorManager; + +IUnityInterfaces* GetUnity(); +ID3D11Device* GetDevice(); +const std::unique_ptr& GetMonitorManager(); \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.cpp b/Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.cpp new file mode 100644 index 0000000..0b66378 --- /dev/null +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.cpp @@ -0,0 +1,259 @@ +#include +#include "Common.h" +#include "MonitorManager.h" +#include "Monitor.h" +#include "Cursor.h" + + +Cursor::Cursor(Monitor* monitor) + : monitor_(monitor) +{ +} + + +Cursor::~Cursor() +{ + if (apiBuffer_ != nullptr) + { + delete[] apiBuffer_; + apiBuffer_ = nullptr; + } + if (bgra32Buffer_ != nullptr) + { + delete[] bgra32Buffer_; + bgra32Buffer_ = nullptr; + } +} + + +void Cursor::Update(const DXGI_OUTDUPL_FRAME_INFO& frameInfo) +{ + isVisible_ = frameInfo.PointerPosition.Visible != 0; + x_ = frameInfo.PointerPosition.Position.x; + y_ = frameInfo.PointerPosition.Position.y; + + // TODO: see more information to check where the cursor is. + if (isVisible_) + { + GetMonitorManager()->SetCursorMonitorId(monitor_->GetId()); + } + + if (GetMonitorManager()->GetCursorMonitorId() != monitor_->GetId()) { + return; + } + + // Increase the buffer size if needed + if (frameInfo.PointerShapeBufferSize > apiBufferSize_) + { + if (apiBuffer_) delete[] apiBuffer_; + apiBuffer_ = new BYTE[frameInfo.PointerShapeBufferSize]; + apiBufferSize_ = frameInfo.PointerShapeBufferSize; + } + if (apiBuffer_ == nullptr) return; + + // Get information about the mouse pointer if needed + if (frameInfo.PointerShapeBufferSize != 0) + { + UINT bufferSize; + monitor_->GetDeskDupl()->GetFramePointerShape( + frameInfo.PointerShapeBufferSize, + reinterpret_cast(apiBuffer_), + &bufferSize, + &shapeInfo_); + } + + // cursor type + const auto cursorType = shapeInfo_.Type; + const bool isMono = cursorType == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME; + const bool isColorMask = cursorType == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR; + + // Size + const auto w = shapeInfo_.Width; + const auto h = shapeInfo_.Height / (isMono ? 2 : 1); + const auto p = shapeInfo_.Pitch; + + // Convert the buffer given by API into BGRA32 + const auto bgraBufferSize = w * h * 4; + if (bgraBufferSize > bgra32BufferSize_) + { + if (bgra32Buffer_) delete[] bgra32Buffer_; + bgra32Buffer_ = new BYTE[bgraBufferSize]; + bgra32BufferSize_ = bgraBufferSize; + } + if (bgra32Buffer_ == nullptr) return; + + // If masked, copy the desktop image and merge it with masked image. + if (isMono || isColorMask) + { + HRESULT hr; + + D3D11_TEXTURE2D_DESC desc; + desc.Width = w; + desc.Height = h; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Usage = D3D11_USAGE_STAGING; + desc.BindFlags = 0; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.MiscFlags = 0; + + ID3D11Texture2D* texture = nullptr; + hr = GetDevice()->CreateTexture2D(&desc, nullptr, &texture); + if (FAILED(hr)) + { + return; + } + + D3D11_BOX box; + box.front = 0; + box.back = 1; + box.left = x_; + box.top = y_; + box.right = x_ + w; + box.bottom = y_ + h; + + ID3D11DeviceContext* context; + GetDevice()->GetImmediateContext(&context); + context->CopySubresourceRegion(texture, 0, 0, 0, 0, monitor_->GetUnityTexture(), 0, &box); + context->Release(); + + IDXGISurface* surface = nullptr; + hr = texture->QueryInterface(__uuidof(IDXGISurface), (void**)&surface); + texture->Release(); + if (FAILED(hr)) + { + return; + } + + DXGI_MAPPED_RECT mappedSurface; + hr = surface->Map(&mappedSurface, DXGI_MAP_READ); + if (FAILED(hr)) + { + surface->Release(); + return; + } + + // Finally, get the texture behind the mouse cursor. + const auto desktop32 = reinterpret_cast(mappedSurface.pBits); + const UINT desktopPitch = mappedSurface.Pitch / sizeof(UINT); + + // Access RGBA values at the same time + auto output32 = reinterpret_cast(bgra32Buffer_); + + if (isMono) + { + for (UINT row = 0; row < h; ++row) + { + BYTE mask = 0x80; + for (UINT col = 0; col < w; ++col) + { + const int i = row * w + col; + const BYTE andMask = apiBuffer_[col / 8 + row * p] & mask; + const BYTE xorMask = apiBuffer_[col / 8 + (row + h) * p] & mask; + const UINT andMask32 = andMask ? 0xFFFFFFFF : 0xFF000000; + const UINT xorMask32 = xorMask ? 0x00FFFFFF : 0x00000000; + output32[i] = (desktop32[row * desktopPitch + col] & andMask32) ^ xorMask32; + mask = (mask == 0x01) ? 0x80 : (mask >> 1); + } + } + } + else // DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR + { + const auto buffer32 = reinterpret_cast(apiBuffer_); + + for (UINT row = 0; row < h; ++row) + { + for (UINT col = 0; col < w; ++col) + { + const int i = row * w + col; + const int j = row * p / sizeof(UINT) + col; + + UINT mask = 0xFF000000 & buffer32[j]; + if (mask) + { + output32[i] = (desktop32[row * desktopPitch + col] ^ buffer32[j]) | 0xFF000000; + } + else + { + output32[i] = buffer32[j] | 0xFF000000; + } + } + } + } + + hr = surface->Unmap(); + surface->Release(); + if (FAILED(hr)) + { + return; + } + } + else // DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR + { + auto output32 = reinterpret_cast(bgra32Buffer_); + const auto buffer32 = reinterpret_cast(apiBuffer_); + for (UINT i = 0; i < w * h; ++i) + { + output32[i] = buffer32[i]; + } + } + + return; +} + + +void Cursor::UpdateTexture(ID3D11Texture2D* texture) +{ + if (bgra32Buffer_ == nullptr) return; + ID3D11DeviceContext* context; + GetDevice()->GetImmediateContext(&context); + context->UpdateSubresource(texture, 0, nullptr, bgra32Buffer_, shapeInfo_.Width * 4, 0); + context->Release(); +} + + +bool Cursor::IsVisible() const +{ + return isVisible_; +} + + +int Cursor::GetX() const +{ + return x_; +} + + +int Cursor::GetY() const +{ + return y_; +} + + +int Cursor::GetWidth() const +{ + return shapeInfo_.Width; +} + + +int Cursor::GetHeight() const +{ + return (shapeInfo_.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME) ? + shapeInfo_.Height / 2 : + shapeInfo_.Height; +} + + +int Cursor::GetPitch() const +{ + return shapeInfo_.Pitch; +} + + +int Cursor::GetType() const +{ + return shapeInfo_.Type; +} diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.h b/Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.h new file mode 100644 index 0000000..37777cc --- /dev/null +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.h @@ -0,0 +1,33 @@ +#include +#include +#include + +class Monitor; + +class Cursor +{ +public: + explicit Cursor(Monitor* monitor); + ~Cursor(); + void Update(const DXGI_OUTDUPL_FRAME_INFO& frameInfo); + void UpdateTexture(ID3D11Texture2D* texture); + + bool IsVisible() const; + int GetX() const; + int GetY() const; + int GetWidth() const; + int GetHeight() const; + int GetPitch() const; + int GetType() const; + +private: + Monitor* monitor_; + bool isVisible_ = false; + int x_ = -1; + int y_ = -1; + BYTE* apiBuffer_ = nullptr; + UINT apiBufferSize_ = 0; + BYTE* bgra32Buffer_ = nullptr; + UINT bgra32BufferSize_ = 0; + DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo_; +}; \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Duplication.cpp b/Plugins/uDesktopDuplication/uDesktopDuplication/Duplication.cpp deleted file mode 100644 index c90741b..0000000 --- a/Plugins/uDesktopDuplication/uDesktopDuplication/Duplication.cpp +++ /dev/null @@ -1,424 +0,0 @@ -#include -#include -#include -#include - -#include "IUnityInterface.h" -#include "IUnityGraphicsD3D11.h" -#include "Duplication.h" - - -Duplication::Duplication(IUnityInterfaces* unity) - : unity_(unity) - , device_(unity->Get()->GetDevice()) -{ - Initialize(); -} - - -Duplication::~Duplication() -{ - Finalize(); - unity_ = nullptr; -} - - -void Duplication::Initialize() -{ - Finalize(); - - // Get factory - IDXGIFactory1* factory; - CreateDXGIFactory1(__uuidof(IDXGIFactory1), reinterpret_cast(&factory)); - - // Check all display adapters - int id = 0; - IDXGIAdapter1* adapter; - for (int i = 0; (factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND); ++i) - { - // Search the main monitor from all outputs - IDXGIOutput* output; - for (int j = 0; (adapter->EnumOutputs(j, &output) != DXGI_ERROR_NOT_FOUND); ++j) - { - Monitor monitor; - monitor.id = id++; - - output->GetDesc(&monitor.outputDesc); - monitor.monitorInfo.cbSize = sizeof(MONITORINFOEX); - GetMonitorInfo(monitor.outputDesc.Monitor, &monitor.monitorInfo); - - auto output1 = reinterpret_cast(output); - output1->DuplicateOutput(device_, &monitor.deskDupl); - output->Release(); - - monitors_.push_back(monitor); - } - - adapter->Release(); - } - - factory->Release(); -} - - -void Duplication::Finalize() -{ - // Release all duplicaitons - for (auto& monitor : monitors_) - { - monitor.deskDupl->Release(); - } - monitors_.clear(); -} - - -bool Duplication::IsValidId(int id) const -{ - return id >= 0 && id < monitors_.size(); -} - - -void Duplication::OnRender(int id) -{ - errorCode_ = 0; - errorMessage_ = ""; - - if (!IsValidId(id)) return; - auto& monitor = monitors_[id]; - - if (monitor.deskDupl == nullptr || monitor.texture == nullptr) return; - - IDXGIResource* resource = nullptr; - DXGI_OUTDUPL_FRAME_INFO frameInfo; - - errorCode_ = monitor.deskDupl->AcquireNextFrame(timeout_, &frameInfo, &resource); - if (FAILED(errorCode_)) - { - if (errorCode_ == DXGI_ERROR_ACCESS_LOST) - { - Initialize(); - errorMessage_ = "[IDXGIOutputDuplication::AcquireNextFrame()] Access lost."; - } - else - { - errorMessage_ = "[IDXGIOutputDuplication::AcquireNextFrame()] Maybe timeout."; - } - return; - } - - ID3D11Texture2D* texture; - resource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast(&texture)); - - ID3D11DeviceContext* context; - device_->GetImmediateContext(&context); - context->CopyResource(monitor.texture, texture); - - if (!UpdateMouse(frameInfo, monitor)) - { - errorCode_ = -999; - errorMessage_ = "[UpdateMouse()] failed."; - } - - resource->Release(); - monitor.deskDupl->ReleaseFrame(); -} - - -bool Duplication::UpdateMouse(const DXGI_OUTDUPL_FRAME_INFO& frameInfo, Monitor& monitor) -{ - auto& pointer = monitor.pointer; - pointer.isVisible = frameInfo.PointerPosition.Visible != 0; - pointer.x = frameInfo.PointerPosition.Position.x; - pointer.y = frameInfo.PointerPosition.Position.y; - - // Pointer type - const auto pointerType = pointer.shapeInfo.Type; - const bool isMono = pointerType == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME; - const bool isColorMask = pointerType == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR; - - if (pointer.isVisible) - { - mouseMonitor_ = monitor.id; - } - - if (mouseMonitor_ != monitor.id) { - return true; - } - - // Increase the buffer size if needed - if (frameInfo.PointerShapeBufferSize > pointer.apiBufferSize) - { - if (pointer.apiBuffer) delete[] pointer.apiBuffer; - pointer.apiBuffer = new BYTE[frameInfo.PointerShapeBufferSize]; - pointer.apiBufferSize = frameInfo.PointerShapeBufferSize; - } - if (!pointer.apiBuffer) return true; - - // Get information about the mouse pointer if needed - if (frameInfo.PointerShapeBufferSize != 0) - { - UINT bufferSize; - monitor.deskDupl->GetFramePointerShape( - frameInfo.PointerShapeBufferSize, - reinterpret_cast(pointer.apiBuffer), - &bufferSize, - &pointer.shapeInfo); - } - - // Size - const auto w = pointer.shapeInfo.Width; - const auto h = pointer.shapeInfo.Height / (isMono ? 2 : 1); - const auto p = pointer.shapeInfo.Pitch; - - // Convert the buffer given by API into BGRA32 - const auto bgraBufferSize = w * h * 4; - if (bgraBufferSize > pointer.bgra32BufferSize) - { - if (pointer.bgra32Buffer) delete[] pointer.bgra32Buffer; - pointer.bgra32Buffer = new BYTE[bgraBufferSize]; - pointer.bgra32BufferSize = bgraBufferSize; - } - if (!pointer.bgra32Buffer) return true; - - // If masked, copy the desktop image and merge it with masked image. - if (isMono || isColorMask) - { - HRESULT hr; - - D3D11_TEXTURE2D_DESC desc; - desc.Width = w; - desc.Height = h; - desc.MipLevels = 1; - desc.ArraySize = 1; - desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - desc.SampleDesc.Count = 1; - desc.SampleDesc.Quality = 0; - desc.Usage = D3D11_USAGE_STAGING; - desc.BindFlags = 0; - desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; - desc.MiscFlags = 0; - - ID3D11DeviceContext* context; - device_->GetImmediateContext(&context); - - ID3D11Texture2D* texture = nullptr; - hr = device_->CreateTexture2D(&desc, nullptr, &texture); - if (FAILED(hr)) - { - return false; - } - - D3D11_BOX box; - box.front = 0; - box.back = 1; - box.left = pointer.x; - box.top = pointer.y; - box.right = pointer.x + w; - box.bottom = pointer.y + h; - context->CopySubresourceRegion(texture, 0, 0, 0, 0, monitor.texture, 0, &box); - - IDXGISurface* surface = nullptr; - hr = texture->QueryInterface(__uuidof(IDXGISurface), (void**)&surface); - texture->Release(); - if (FAILED(hr)) - { - return false; - } - - DXGI_MAPPED_RECT mappedSurface; - hr = surface->Map(&mappedSurface, DXGI_MAP_READ); - if (FAILED(hr)) - { - surface->Release(); - return false; - } - - // Finally, get the texture behind the mouse pointer. - const auto desktop32 = reinterpret_cast(mappedSurface.pBits); - const UINT desktopPitch = mappedSurface.Pitch / sizeof(UINT); - - // Access RGBA values at the same time - auto output32 = reinterpret_cast(pointer.bgra32Buffer); - - if (isMono) - { - for (UINT row = 0; row < h; ++row) - { - BYTE mask = 0x80; - for (UINT col = 0; col < w; ++col) - { - const int i = row * w + col; - const BYTE andMask = pointer.apiBuffer[col / 8 + row * p] & mask; - const BYTE xorMask = pointer.apiBuffer[col / 8 + (row + h) * p] & mask; - const UINT andMask32 = andMask ? 0xFFFFFFFF : 0xFF000000; - const UINT xorMask32 = xorMask ? 0x00FFFFFF : 0x00000000; - output32[i] = (desktop32[row * desktopPitch + col] & andMask32) ^ xorMask32; - mask = (mask == 0x01) ? 0x80 : (mask >> 1); - } - } - } - else // DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR - { - const auto buffer32 = reinterpret_cast(pointer.apiBuffer); - - for (UINT row = 0; row < h; ++row) - { - for (UINT col = 0; col < w; ++col) - { - const int i = row * w + col; - const int j = row * p / sizeof(UINT) + col; - - UINT mask = 0xFF000000 & buffer32[j]; - if (mask) - { - output32[i] = (desktop32[row * desktopPitch + col] ^ buffer32[j]) | 0xFF000000; - } - else - { - output32[i] = buffer32[j] | 0xFF000000; - } - } - } - } - - hr = surface->Unmap(); - surface->Release(); - if (FAILED(hr)) - { - return false; - } - } - else // DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR - { - auto output32 = reinterpret_cast(pointer.bgra32Buffer); - const auto buffer32 = reinterpret_cast(pointer.apiBuffer); - for (UINT i = 0; i < w * h; ++i) - { - output32[i] = buffer32[i]; - } - } - - return true; -} - - -void Duplication::UpdatePointerTexture(int id, ID3D11Texture2D* ptr) -{ - if (!IsValidId(id)) return; - const auto& pointer = monitors_[id].pointer; - if (!pointer.bgra32Buffer) return; - ID3D11DeviceContext* context; - device_->GetImmediateContext(&context); - context->UpdateSubresource(ptr, 0, nullptr, pointer.bgra32Buffer, pointer.shapeInfo.Width * 4, 0); -} - - -void Duplication::SetTimeout(int timeout) -{ - timeout_ = timeout; -} - - -void Duplication::SetTexturePtr(int id, void* texture) -{ - if (!IsValidId(id)) return; - monitors_[id].texture = reinterpret_cast(texture); -} - - -int Duplication::GetMonitorCount() const -{ - return static_cast(monitors_.size()); -} - - -void Duplication::GetName(int id, char* buf, int len) const -{ - if (!IsValidId(id)) return; - strcpy_s(buf, len, monitors_[id].monitorInfo.szDevice); -} - - -bool Duplication::IsPrimary(int id) const -{ - if (!IsValidId(id)) return false; - return monitors_[id].monitorInfo.dwFlags == MONITORINFOF_PRIMARY; -} - - -int Duplication::GetWidth(int id) const -{ - if (!IsValidId(id)) return -1; - const auto rect = monitors_[id].monitorInfo.rcMonitor; - return rect.right - rect.left; -} - - -int Duplication::GetHeight(int id) const -{ - if (!IsValidId(id)) return -1; - const auto rect = monitors_[id].monitorInfo.rcMonitor; - return rect.bottom - rect.top; -} - - -int Duplication::IsPointerVisible(int id) const -{ - if (!IsValidId(id)) return false; - return monitors_[id].pointer.isVisible; -} - - -int Duplication::GetPointerX(int id) const -{ - if (!IsValidId(id)) return -1; - return monitors_[id].pointer.x; -} - - -int Duplication::GetPointerY(int id) const -{ - if (!IsValidId(id)) return -1; - return monitors_[id].pointer.y; -} - - -int Duplication::GetPointerShapeWidth(int id) const -{ - if (!IsValidId(id)) return -1; - return monitors_[id].pointer.shapeInfo.Width; -} - - -int Duplication::GetPointerShapeHeight(int id) const -{ - if (!IsValidId(id)) return -1; - const auto& info = monitors_[id].pointer.shapeInfo; - return (info.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME) ? info.Height / 2 : info.Height; -} - - -int Duplication::GetPointerShapePitch(int id) const -{ - if (!IsValidId(id)) return -1; - return monitors_[id].pointer.shapeInfo.Pitch; -} - - -int Duplication::GetPointerShapeType(int id) const -{ - if (!IsValidId(id)) return -1; - return monitors_[id].pointer.shapeInfo.Type; -} - - -int Duplication::GetErrorCode() const -{ - return errorCode_; -} - - -void Duplication::GetErrorMessage(char* buf, int len) const -{ - strcpy_s(buf, len, errorMessage_.c_str()); -} \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Duplication.h b/Plugins/uDesktopDuplication/uDesktopDuplication/Duplication.h deleted file mode 100644 index 1eba567..0000000 --- a/Plugins/uDesktopDuplication/uDesktopDuplication/Duplication.h +++ /dev/null @@ -1,72 +0,0 @@ -#include -#include -#include -#include - -struct IUnityInterfaces; - -struct Pointer -{ - bool isVisible = false; - int x = -1; - int y = -1; - BYTE* apiBuffer = nullptr; - UINT apiBufferSize = 0; - BYTE* bgra32Buffer = nullptr; - UINT bgra32BufferSize = 0; - DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo; -}; - -struct Monitor -{ - int id = -1; - IDXGIOutputDuplication* deskDupl = nullptr; - ID3D11Texture2D* texture = nullptr; - DXGI_OUTPUT_DESC outputDesc; - MONITORINFOEX monitorInfo; - Pointer pointer; -}; - -class Duplication -{ -public: - explicit Duplication(IUnityInterfaces* unity); - ~Duplication(); - void OnRender(int id); - -public: - void UpdatePointerTexture(int id, ID3D11Texture2D* ptr); - void SetTimeout(int timeout); - void SetTexturePtr(int id, void* texture); - -public: - int GetMonitorCount() const; - void GetName(int id, char* buf, int len) const; - bool IsPrimary(int id) const; - int GetWidth(int id) const; - int GetHeight(int id) const; - int IsPointerVisible(int id) const; - int GetPointerX(int id) const; - int GetPointerY(int id) const; - int GetPointerShapeWidth(int id) const; - int GetPointerShapeHeight(int id) const; - int GetPointerShapePitch(int id) const; - int GetPointerShapeType(int id) const; - int GetErrorCode() const; - void GetErrorMessage(char* buf, int len) const; - -private: - void Initialize(); - void Finalize(); - bool UpdateMouse(const DXGI_OUTDUPL_FRAME_INFO& frameInfo, Monitor& monitor); - - bool IsValidId(int id) const; - - IUnityInterfaces* unity_ = nullptr; - ID3D11Device* device_ = nullptr; - int mouseMonitor_ = 0; - int timeout_ = 10; - HRESULT errorCode_ = 0; - std::string errorMessage_ = ""; - std::vector monitors_; -}; \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.cpp b/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.cpp new file mode 100644 index 0000000..5883d6b --- /dev/null +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.cpp @@ -0,0 +1,118 @@ +#include +#include "Common.h" +#include "Cursor.h" +#include "MonitorManager.h" +#include "Monitor.h" + + +Monitor::Monitor(int id, IDXGIOutput* output) + : id_(id) +{ + // MEMO: 'output' will be released automatically after this ctor. + output->GetDesc(&outputDesc_); + monitorInfo_.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfo(outputDesc_.Monitor, &monitorInfo_); + + auto output1 = reinterpret_cast(output); + output1->DuplicateOutput(GetDevice(), &deskDupl_); + + cursor_ = std::make_unique(this); +} + + +Monitor::~Monitor() +{ + deskDupl_->Release(); +} + + +HRESULT Monitor::Render(UINT timeout) +{ + if (deskDupl_ == nullptr || unityTexture_ == nullptr) return 0; + + IDXGIResource* resource = nullptr; + DXGI_OUTDUPL_FRAME_INFO frameInfo; + + const auto hr = deskDupl_->AcquireNextFrame(timeout, &frameInfo, &resource); + if (FAILED(hr)) + { + return hr; + } + + ID3D11Texture2D* texture; + resource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast(&texture)); + + ID3D11DeviceContext* context; + GetDevice()->GetImmediateContext(&context); + context->CopyResource(unityTexture_, texture); + context->Release(); + + cursor_->Update(frameInfo); + + resource->Release(); + deskDupl_->ReleaseFrame(); + + return 0; +} + + +int Monitor::GetId() const +{ + return id_; +} + + +void Monitor::SetUnityTexture(ID3D11Texture2D* texture) +{ + unityTexture_ = texture; +} + + +ID3D11Texture2D* Monitor::GetUnityTexture() const +{ + return unityTexture_; +} + + +IDXGIOutputDuplication* Monitor::GetDeskDupl() +{ + return deskDupl_; +} + + +const std::unique_ptr& Monitor::GetCursor() +{ + return cursor_; +} + + +void Monitor::UpdateCursorTexture(ID3D11Texture2D* texture) +{ + cursor_->UpdateTexture(texture); +} + + +void Monitor::GetName(char* buf, int len) const +{ + strcpy_s(buf, len, monitorInfo_.szDevice); +} + + +bool Monitor::IsPrimary() const +{ + return monitorInfo_.dwFlags == MONITORINFOF_PRIMARY; +} + + +int Monitor::GetWidth() const +{ + const auto rect = monitorInfo_.rcMonitor; + return rect.right - rect.left; +} + + +int Monitor::GetHeight() const +{ + const auto rect = monitorInfo_.rcMonitor; + return rect.bottom - rect.top; +} \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.h b/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.h new file mode 100644 index 0000000..e3066ef --- /dev/null +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.h @@ -0,0 +1,33 @@ +#include +#include +#include + +class Cursor; + +class Monitor +{ +public: + Monitor(int id, IDXGIOutput* output); + ~Monitor(); + HRESULT Render(UINT timeout = 0); + void UpdateCursorTexture(ID3D11Texture2D* texture); + +public: + int GetId() const; + void SetUnityTexture(ID3D11Texture2D* texture); + ID3D11Texture2D* GetUnityTexture() const; + void GetName(char* buf, int len) const; + bool IsPrimary() const; + int GetWidth() const; + int GetHeight() const; + IDXGIOutputDuplication* GetDeskDupl(); + const std::unique_ptr& GetCursor(); + +private: + int id_ = -1; + std::unique_ptr cursor_; + IDXGIOutputDuplication* deskDupl_ = nullptr; + ID3D11Texture2D* unityTexture_ = nullptr; + DXGI_OUTPUT_DESC outputDesc_; + MONITORINFOEX monitorInfo_; +}; \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.cpp b/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.cpp new file mode 100644 index 0000000..8f9b2dc --- /dev/null +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.cpp @@ -0,0 +1,221 @@ +#include +#include +#include +#include + +#include "IUnityInterface.h" +#include "IUnityGraphicsD3D11.h" + +#include "Monitor.h" +#include "Cursor.h" +#include "MonitorManager.h" + + +MonitorManager::MonitorManager() +{ + Initialize(); +} + + +MonitorManager::~MonitorManager() +{ + Finalize(); +} + + +void MonitorManager::Initialize() +{ + Finalize(); + + // Get factory + IDXGIFactory1* factory; + CreateDXGIFactory1(__uuidof(IDXGIFactory1), reinterpret_cast(&factory)); + + // Check all display adapters + int id = 0; + IDXGIAdapter1* adapter; + for (int i = 0; (factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND); ++i) + { + // Search the main monitor from all outputs + IDXGIOutput* output; + for (int j = 0; (adapter->EnumOutputs(j, &output) != DXGI_ERROR_NOT_FOUND); ++j) + { + auto monitor = std::make_shared(id++, output); + monitors_.push_back(monitor); + output->Release(); + } + adapter->Release(); + } + factory->Release(); +} + + +void MonitorManager::Finalize() +{ + monitors_.clear(); +} + + +std::shared_ptr MonitorManager::GetMonitor(int id) const +{ + if (id >= 0 && id < monitors_.size()) + { + return monitors_[id]; + } + return nullptr; +} + + +void MonitorManager::OnRender(int id) +{ + if (auto monitor = GetMonitor(id)) + { + // If any monitor setting has changed (e.g. monitor size has changed), + // it is necessary to re-initialize monitors. + const auto hr = monitor->Render(timeout_); + if (hr == DXGI_ERROR_ACCESS_LOST) + { + Initialize(); + } + } +} + + +void MonitorManager::UpdateCursorTexture(int id, ID3D11Texture2D* texture) +{ + if (auto monitor = GetMonitor(id)) + { + monitor->UpdateCursorTexture(texture); + } +} + + +void MonitorManager::SetTimeout(int timeout) +{ + timeout_ = timeout; +} + + +void MonitorManager::SetTexturePtr(int id, void* texture) +{ + if (auto monitor = GetMonitor(id)) + { + auto d3d11Texture = reinterpret_cast(texture); + monitor->SetUnityTexture(d3d11Texture); + } +} + + +int MonitorManager::GetMonitorCount() const +{ + return static_cast(monitors_.size()); +} + + +void MonitorManager::GetName(int id, char* buf, int len) const +{ + if (auto monitor = GetMonitor(id)) + { + monitor->GetName(buf, len); + } +} + + +bool MonitorManager::IsPrimary(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->IsPrimary(); + } + return false; +} + + +int MonitorManager::GetWidth(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetWidth(); + } + return -1; +} + + +int MonitorManager::GetHeight(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetHeight(); + } + return -1; +} + + +int MonitorManager::IsCursorVisible(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetCursor()->IsVisible(); + } + return false; +} + + +int MonitorManager::GetCursorX(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetCursor()->GetX(); + } + return -1; +} + + +int MonitorManager::GetCursorY(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetCursor()->GetY(); + } + return -1; +} + + +int MonitorManager::GetCursorShapeWidth(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetCursor()->GetWidth(); + } + return -1; +} + + +int MonitorManager::GetCursorShapeHeight(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetCursor()->GetHeight(); + } + return -1; +} + + +int MonitorManager::GetCursorShapePitch(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetCursor()->GetPitch(); + } + return -1; +} + + +int MonitorManager::GetCursorShapeType(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetCursor()->GetType(); + } + return -1; +} \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.h b/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.h new file mode 100644 index 0000000..08b9e9b --- /dev/null +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.h @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include + +struct IUnityInterfaces; +class Monitor; +class Cursor; + +class MonitorManager +{ +public: + explicit MonitorManager(); + ~MonitorManager(); + + void SetCursorMonitorId(int id) { cursorMonitorId_ = id; } + int GetCursorMonitorId() const { return cursorMonitorId_; } + std::shared_ptr GetMonitor(int id) const; + +private: + void Initialize(); + void Finalize(); + +// Setters from Unity +public: + void OnRender(int id); + void UpdateCursorTexture(int id, ID3D11Texture2D* ptr); + void SetTimeout(int timeout); + void SetTexturePtr(int id, void* texture); + +// Getters from Unity +public: + int GetMonitorCount() const; + void GetName(int id, char* buf, int len) const; + bool IsPrimary(int id) const; + int GetWidth(int id) const; + int GetHeight(int id) const; + int IsCursorVisible(int id) const; + int GetCursorX(int id) const; + int GetCursorY(int id) const; + int GetCursorShapeWidth(int id) const; + int GetCursorShapeHeight(int id) const; + int GetCursorShapePitch(int id) const; + int GetCursorShapeType(int id) const; + +private: + int timeout_ = 10; + std::vector> monitors_; + std::shared_ptr cursor_; + int cursorMonitorId_ = -1; +}; \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/IUnityGraphics.h b/Plugins/uDesktopDuplication/uDesktopDuplication/include/IUnityGraphics.h similarity index 100% rename from Plugins/uDesktopDuplication/uDesktopDuplication/IUnityGraphics.h rename to Plugins/uDesktopDuplication/uDesktopDuplication/include/IUnityGraphics.h diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/IUnityGraphicsD3D11.h b/Plugins/uDesktopDuplication/uDesktopDuplication/include/IUnityGraphicsD3D11.h similarity index 100% rename from Plugins/uDesktopDuplication/uDesktopDuplication/IUnityGraphicsD3D11.h rename to Plugins/uDesktopDuplication/uDesktopDuplication/include/IUnityGraphicsD3D11.h diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/IUnityInterface.h b/Plugins/uDesktopDuplication/uDesktopDuplication/include/IUnityInterface.h similarity index 100% rename from Plugins/uDesktopDuplication/uDesktopDuplication/IUnityInterface.h rename to Plugins/uDesktopDuplication/uDesktopDuplication/include/IUnityInterface.h diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/main.cpp b/Plugins/uDesktopDuplication/uDesktopDuplication/main.cpp index 4ee35df..03f5d78 100644 --- a/Plugins/uDesktopDuplication/uDesktopDuplication/main.cpp +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/main.cpp @@ -6,14 +6,35 @@ #include "IUnityInterface.h" #include "IUnityGraphics.h" -#include "Duplication.h" +#include "IUnityGraphicsD3D11.h" + +#include "Common.h" +#include "MonitorManager.h" #pragma comment(lib, "dxgi.lib") namespace { - std::unique_ptr g_dupl; + IUnityInterfaces* g_unity = nullptr; + std::unique_ptr g_manager; +} + + +IUnityInterfaces* GetUnity() +{ + return g_unity; +} + + +ID3D11Device* GetDevice() +{ + return GetUnity()->Get()->GetDevice(); +} + +const std::unique_ptr& GetMonitorManager() +{ + return g_manager; } @@ -21,17 +42,18 @@ extern "C" { UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces) { - g_dupl = std::make_unique(unityInterfaces); + g_unity = unityInterfaces; + g_manager = std::make_unique(); } UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UnityPluginUnload() { - g_dupl.reset(); + g_manager.reset(); } void UNITY_INTERFACE_API OnRenderEvent(int id) { - g_dupl->OnRender(id); + g_manager->OnRender(id); } UNITY_INTERFACE_EXPORT UnityRenderingEvent UNITY_INTERFACE_API GetRenderEventFunc() @@ -41,87 +63,76 @@ extern "C" UNITY_INTERFACE_EXPORT size_t UNITY_INTERFACE_API GetMonitorCount() { - return g_dupl->GetMonitorCount(); + return g_manager->GetMonitorCount(); } UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API SetTimeout(int timeout) { - g_dupl->SetTimeout(timeout); + g_manager->SetTimeout(timeout); } UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API GetName(int id, char* buf, int len) { - g_dupl->GetName(id, buf, len); + g_manager->GetName(id, buf, len); } UNITY_INTERFACE_EXPORT bool UNITY_INTERFACE_API IsPrimary(int id) { - return g_dupl->IsPrimary(id); + return g_manager->IsPrimary(id); } UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetWidth(int id) { - return g_dupl->GetWidth(id); + return g_manager->GetWidth(id); } UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetHeight(int id) { - return g_dupl->GetHeight(id); + return g_manager->GetHeight(id); } - UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API IsPointerVisible(int id) + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API IsCursorVisible(int id) { - return g_dupl->IsPointerVisible(id); + return g_manager->IsCursorVisible(id); } - UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerX(int id) + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetCursorX(int id) { - return g_dupl->GetPointerX(id); + return g_manager->GetCursorX(id); } - UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerY(int id) + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetCursorY(int id) { - return g_dupl->GetPointerY(id); + return g_manager->GetCursorY(id); } - UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeWidth(int id) + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetCursorShapeWidth(int id) { - return g_dupl->GetPointerShapeWidth(id); + return g_manager->GetCursorShapeWidth(id); } - UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeHeight(int id) + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetCursorShapeHeight(int id) { - return g_dupl->GetPointerShapeHeight(id); + return g_manager->GetCursorShapeHeight(id); } - UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapePitch(int id) + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetCursorShapePitch(int id) { - return g_dupl->GetPointerShapePitch(id); + return g_manager->GetCursorShapePitch(id); } - UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeType(int id) + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetCursorShapeType(int id) { - return g_dupl->GetPointerShapeType(id); + return g_manager->GetCursorShapeType(id); } - UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UpdatePointerTexture(int id, ID3D11Texture2D* ptr) + UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UpdateCursorTexture(int id, ID3D11Texture2D* ptr) { - g_dupl->UpdatePointerTexture(id, ptr); + g_manager->UpdateCursorTexture(id, ptr); } UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API SetTexturePtr(int id, void* texture) { - g_dupl->SetTexturePtr(id, texture); + g_manager->SetTexturePtr(id, texture); } - - UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetErrorCode() - { - return g_dupl->GetErrorCode(); - } - - UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API GetErrorMessage(char* buf, int len) - { - g_dupl->GetErrorMessage(buf, len); - } - } \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/uDesktopDuplication.vcxproj b/Plugins/uDesktopDuplication/uDesktopDuplication/uDesktopDuplication.vcxproj index ce95a48..b74e58c 100644 --- a/Plugins/uDesktopDuplication/uDesktopDuplication/uDesktopDuplication.vcxproj +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/uDesktopDuplication.vcxproj @@ -69,8 +69,12 @@ - - + + $(VC_IncludePath);$(WindowsSDK_IncludePath);include + + + $(VC_IncludePath);$(WindowsSDK_IncludePath);include + Level3 @@ -122,14 +126,19 @@ - - + + + + - - - - + + + + + + + diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/uDesktopDuplication.vcxproj.filters b/Plugins/uDesktopDuplication/uDesktopDuplication/uDesktopDuplication.vcxproj.filters new file mode 100644 index 0000000..bcdb742 --- /dev/null +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/uDesktopDuplication.vcxproj.filters @@ -0,0 +1,29 @@ + + + + + {a820eab3-2c2a-4d56-9670-064198e6a1cf} + + + + + Include + + + Include + + + Include + + + + + + + + + + + + + \ No newline at end of file