├── .gitattributes ├── src ├── main.ico ├── meta │ ├── comptr.h │ ├── fromByteSpan.h │ ├── scope_guard.h │ ├── member_method.h │ ├── callback_adapter.h │ └── flags.h ├── main.rc ├── win32 │ ├── PowerRequest.cpp │ ├── Handle.h │ ├── Dpi.h │ ├── Process.h │ ├── Geometry.ostream.h │ ├── Thread.cpp │ ├── Window.ostream.h │ ├── Process.cpp │ ├── Thread.h │ ├── DisplayMonitor.cpp │ ├── WaitableTimer.cpp │ ├── ThreadLoop.cpp │ ├── DisplayMonitor.h │ ├── PowerRequest.h │ ├── TaskbarList.h │ ├── WaitableTimer.h │ ├── Geometry.h │ ├── ThreadLoop.h │ ├── TaskbarList.cpp │ ├── Window.h │ ├── Window.cpp │ └── WindowMessageHandler.h ├── PlainPixelShader.hlsl ├── FrameContext.h ├── MainThread.cpp ├── stable.h ├── main.cpp ├── VertexShader.hlsl ├── MainThread.h ├── PointerUpdater.h ├── main.manifest ├── MaskedPixelShader.hlsl ├── CaptureAreaWindow.h ├── RenderThread.h ├── PointerUpdater.cpp ├── Model.cpp ├── FrameUpdater.h ├── TaskbarButtons.h ├── CapturedUpdate.h ├── renderer.h ├── MainController.h ├── MainApplication.h ├── RenderThread.cpp ├── CaptureAreaWindow.cpp ├── OutputWindow.h ├── BaseRenderer.h ├── CaptureThread.h ├── DuplicationController.h ├── TaskbarButtons.cpp ├── Model.h ├── WindowRenderer.h ├── BaseRenderer.cpp ├── CaptureThread.cpp ├── FrameUpdater.cpp ├── renderer.cpp ├── DuplicationController.cpp ├── WindowRenderer.cpp └── MainApplication.cpp ├── docs ├── usage-schema.png ├── usage-schema.pptx └── duplication-icon.psd ├── .gitignore ├── .editorconfig ├── LICENSE ├── .clang-format ├── .github └── workflows │ └── main.yml ├── qbs └── modules │ └── hlsl │ └── hlsl.qbs ├── README.adoc └── DesktopDuplicator.qbs /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | *.rb eol=lf 4 | -------------------------------------------------------------------------------- /src/main.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arBmind/desktop-duplication-cpp/HEAD/src/main.ico -------------------------------------------------------------------------------- /src/meta/comptr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | using Microsoft::WRL::ComPtr; 5 | -------------------------------------------------------------------------------- /docs/usage-schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arBmind/desktop-duplication-cpp/HEAD/docs/usage-schema.png -------------------------------------------------------------------------------- /docs/usage-schema.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arBmind/desktop-duplication-cpp/HEAD/docs/usage-schema.pptx -------------------------------------------------------------------------------- /docs/duplication-icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arBmind/desktop-duplication-cpp/HEAD/docs/duplication-icon.psd -------------------------------------------------------------------------------- /src/main.rc: -------------------------------------------------------------------------------- 1 | #include "winuser.h" 2 | CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST main.manifest 3 | desk1 ICON "main.ico" 4 | -------------------------------------------------------------------------------- /src/win32/PowerRequest.cpp: -------------------------------------------------------------------------------- 1 | #include "PowerRequest.h" 2 | 3 | namespace win32 { 4 | 5 | // empty 6 | 7 | } // namespace win32 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.user 2 | .vs/ 3 | x64/Debug/ 4 | x64/Release/ 5 | VertexShader.h 6 | PixelShader.h 7 | *PixelShader.h 8 | packages/ 9 | *.user.* 10 | -------------------------------------------------------------------------------- /src/PlainPixelShader.hlsl: -------------------------------------------------------------------------------- 1 | Texture2D texture0; 2 | SamplerState sampler0; 3 | 4 | struct Input { 5 | float4 position : SV_POSITION; 6 | float2 texCoord : TEXCOORD; 7 | }; 8 | 9 | float4 main(Input input) : SV_TARGET { 10 | return texture0.Sample(sampler0, input.texCoord); 11 | } 12 | -------------------------------------------------------------------------------- /src/FrameContext.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "win32/Geometry.h" 3 | 4 | #include 5 | 6 | using win32::Point; 7 | 8 | struct FrameContext { 9 | Point offset{}; // offset from desktop to target coordinates 10 | DXGI_OUTPUT_DESC output_desc{}; // description of the frame 11 | }; 12 | -------------------------------------------------------------------------------- /src/win32/Handle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | 6 | namespace win32 { 7 | 8 | struct HandleCloser { 9 | void operator()(HANDLE handle) const { ::CloseHandle(handle); } 10 | }; 11 | using Handle = std::unique_ptr; 12 | 13 | } // namespace win32 14 | -------------------------------------------------------------------------------- /src/win32/Dpi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace win32 { 4 | 5 | enum class DpiAwareness { 6 | Unknown, 7 | Unaware, 8 | UnawareGdiScaled, 9 | SystemAware, 10 | PerMonitorAware, 11 | PerMonitorAwareV2, 12 | }; 13 | struct Dpi { 14 | unsigned value; 15 | }; 16 | 17 | } // namespace win32 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | 11 | [*.rc] 12 | end_of_line = crlf # this would not compile with lf 13 | 14 | [*.{cpp,h}] 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /src/meta/fromByteSpan.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | template 6 | auto fromByteSpan(std::span &src) -> S { 7 | using T = typename S::element_type; 8 | static_assert(alignof(T) <= sizeof(T)); 9 | return S{std::bit_cast(src.data()), src.size() / sizeof(T)}; 10 | } 11 | -------------------------------------------------------------------------------- /src/MainThread.cpp: -------------------------------------------------------------------------------- 1 | #include "MainThread.h" 2 | 3 | namespace deskdup { 4 | 5 | MainThread::MainThread(win32::WindowClass::Config windowClassConfig) 6 | : m_windowClass{windowClassConfig} 7 | , m_thread{win32::Thread::fromCurrent()} { 8 | 9 | m_threadLoop.enableAwaitAlerts(); 10 | } 11 | 12 | int MainThread::run() { return m_threadLoop.run(); } 13 | 14 | } // namespace deskdup 15 | -------------------------------------------------------------------------------- /src/stable.h: -------------------------------------------------------------------------------- 1 | #pragma 2 | 3 | #ifndef NOMINMAX 4 | # define NOMINMAX 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include // GET_X_LPARAM 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | -------------------------------------------------------------------------------- /src/win32/Process.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Dpi.h" 3 | 4 | #include 5 | 6 | namespace win32 { 7 | 8 | /// Wrapper for a win32 Process 9 | struct Process { 10 | /// set default DpiAwareness for current process 11 | /// note: WindowClasses and Threads use it (set it before creating them) 12 | static void current_setDpiAwareness(DpiAwareness); 13 | 14 | private: 15 | // HANDLE m_processHandle{}; 16 | }; 17 | 18 | } // namespace win32 19 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "MainApplication.h" 2 | 3 | #include "win32/Process.h" 4 | 5 | auto WINAPI WinMain(_In_ HINSTANCE instanceHandle, _In_opt_ HINSTANCE, _In_ LPSTR, _In_ int /*showCommand*/) -> int { 6 | win32::Process::current_setDpiAwareness(win32::DpiAwareness::PerMonitorAwareV2); 7 | { 8 | const auto hr = CoInitialize(nullptr); 9 | if (!SUCCEEDED(hr)) return -1; 10 | } 11 | auto app = deskdup::MainApplication{instanceHandle}; 12 | return app.run(); 13 | } 14 | -------------------------------------------------------------------------------- /src/win32/Geometry.ostream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Geometry.h" 3 | 4 | #include 5 | 6 | namespace win32 { 7 | 8 | auto operator<<(std::ostream &out, Point p) -> std::ostream & { return out << "[" << p.x << ", " << p.y << "]"; } 9 | 10 | auto operator<<(std::ostream &out, Dimension d) -> std::ostream & { 11 | return out << "[" << d.width << ", " << d.height << "]"; 12 | } 13 | 14 | auto operator<<(std::ostream &out, Rect r) -> std::ostream & { 15 | return out << "Rect{topLeft: " << r.topLeft << ", dimension: " << r.dimension << "}"; 16 | } 17 | 18 | } // namespace win32 19 | -------------------------------------------------------------------------------- /src/VertexShader.hlsl: -------------------------------------------------------------------------------- 1 | struct VertexInput { 2 | float2 position : POSITION; 3 | float2 texCoord : TEXCOORD; 4 | }; 5 | 6 | struct VertexOutput { 7 | float4 position : SV_POSITION; 8 | float2 texCoord : TEXCOORD0; 9 | float2 bgCoord : TEXCOORD1; 10 | }; 11 | 12 | float2 posToCoord(float2 pos) { 13 | return float2( 14 | (1 + pos.x) / 2, 15 | (1 - pos.y) / 2 16 | ); 17 | } 18 | 19 | VertexOutput main(VertexInput input) { 20 | VertexOutput ret; 21 | ret.position = float4(input.position, 0, 1); 22 | ret.texCoord = input.texCoord; 23 | ret.bgCoord = posToCoord(input.position); 24 | return ret; 25 | } 26 | -------------------------------------------------------------------------------- /src/MainThread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "win32/Thread.h" 3 | #include "win32/ThreadLoop.h" 4 | #include "win32/Window.h" 5 | 6 | namespace deskdup { 7 | 8 | struct MainThread final { 9 | explicit MainThread(win32::WindowClass::Config windowClassConfig); 10 | 11 | // note: only use the windowClass from the main thread! 12 | auto windowClass() -> win32::WindowClass & { return m_windowClass; } 13 | 14 | auto thread() -> win32::Thread & { return m_thread; } 15 | auto threadLoop() -> win32::ThreadLoop & { return m_threadLoop; } 16 | 17 | int run(); 18 | 19 | private: 20 | win32::WindowClass m_windowClass; 21 | win32::Thread m_thread{}; 22 | win32::ThreadLoop m_threadLoop{}; 23 | }; 24 | 25 | } // namespace deskdup 26 | -------------------------------------------------------------------------------- /src/win32/Thread.cpp: -------------------------------------------------------------------------------- 1 | #include "Thread.h" 2 | 3 | #include 4 | 5 | namespace win32 { 6 | namespace { 7 | 8 | auto GetCurrentThreadHandle() -> HANDLE { 9 | auto output = HANDLE{}; 10 | const auto process = GetCurrentProcess(); 11 | const auto thread = GetCurrentThread(); 12 | const auto desiredAccess = 0; 13 | const auto inheritHandle = false; 14 | const auto options = DUPLICATE_SAME_ACCESS; 15 | const auto success = 16 | DuplicateHandle(process, thread, process, &output, desiredAccess, inheritHandle, options); 17 | (void)success; 18 | return output; 19 | } 20 | 21 | } // namespace 22 | 23 | auto Thread::fromCurrent() -> Thread { return Thread{GetCurrentThreadHandle()}; } 24 | 25 | } // namespace win32 26 | -------------------------------------------------------------------------------- /src/meta/scope_guard.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | template 4 | struct scope_guard { 5 | scope_guard(T &&f) noexcept 6 | : t((T &&) f) {} 7 | ~scope_guard() noexcept { t(); } 8 | 9 | scope_guard(const scope_guard &) = delete; 10 | auto operator=(const scope_guard &) -> scope_guard & = delete; 11 | scope_guard(scope_guard &&) = default; 12 | auto operator=(scope_guard &&) -> scope_guard & = default; 13 | 14 | private: 15 | T t; 16 | }; 17 | 18 | template 19 | auto make_scope_guard(T &&f) noexcept { 20 | return scope_guard((T &&) f); 21 | } 22 | 23 | #define SCOPE_GUARD_CAT(x, y) x##y 24 | #define SCOPE_GUARD_ID(index) SCOPE_GUARD_CAT(__scope_guard, index) 25 | 26 | #define LATER(expr) auto SCOPE_GUARD_ID(__LINE__){make_scope_guard([&]() noexcept { expr; })}; 27 | -------------------------------------------------------------------------------- /src/win32/Window.ostream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Window.h" 3 | 4 | #include "Geometry.ostream.h" 5 | 6 | #include 7 | 8 | namespace win32 { 9 | 10 | auto operator<<(std::ostream &out, Window::ShowState state) -> std::ostream & { 11 | switch (state) { 12 | case Window::ShowState::Normal: return out << "Normal"; 13 | case Window::ShowState::Minimized: return out << "Minimized"; 14 | case Window::ShowState::Maximized: return out << "Maximized"; 15 | } 16 | return out << "[unknown]"; 17 | } 18 | 19 | auto operator<<(std::ostream &out, const Window::Placement &p) -> std::ostream & { 20 | return out << "Placement{showState: " << p.showState << "," 21 | << " current: " << p.currentRect << "," 22 | << " normal: " << p.normalRect << "}"; 23 | } 24 | 25 | } // namespace win32 26 | -------------------------------------------------------------------------------- /src/PointerUpdater.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | struct PointerUpdate; 8 | struct FrameContext; 9 | 10 | struct PointerBuffer { 11 | uint64_t position_timestamp = 0; // timestamp of the last postition & visible update 12 | POINT position{}; 13 | bool visible = false; 14 | 15 | uint64_t shape_timestamp = 0; // timestamp of last shape update 16 | DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info{}; 17 | std::vector shape_data{}; // buffer for with shape texture 18 | }; 19 | 20 | struct PointerUpdater { 21 | void update(PointerUpdate &update, const FrameContext &context); 22 | 23 | auto data() const noexcept -> const PointerBuffer & { return m_pointer; } 24 | 25 | private: 26 | RECT m_pointer_desktop = {0, 0, 0, 0}; 27 | PointerBuffer m_pointer; 28 | }; 29 | -------------------------------------------------------------------------------- /src/win32/Process.cpp: -------------------------------------------------------------------------------- 1 | #include "Process.h" 2 | 3 | namespace win32 { 4 | 5 | void Process::current_setDpiAwareness(DpiAwareness dpiAwareness) { 6 | auto dpiAwarenessContext = [&]() -> DPI_AWARENESS_CONTEXT { 7 | switch (dpiAwareness) { 8 | case DpiAwareness::Unaware: return DPI_AWARENESS_CONTEXT_UNAWARE; 9 | case DpiAwareness::UnawareGdiScaled: return DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED; 10 | case DpiAwareness::SystemAware: return DPI_AWARENESS_CONTEXT_SYSTEM_AWARE; 11 | case DpiAwareness::PerMonitorAware: return DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE; 12 | case DpiAwareness::PerMonitorAwareV2: return DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2; 13 | case DpiAwareness::Unknown: return nullptr; 14 | } 15 | return nullptr; 16 | }(); 17 | ::SetProcessDpiAwarenessContext(dpiAwarenessContext); 18 | } 19 | 20 | } // namespace win32 21 | -------------------------------------------------------------------------------- /src/main.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | True/PM 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/win32/Thread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Handle.h" 3 | 4 | #include "meta/callback_adapter.h" 5 | 6 | namespace win32 { 7 | 8 | /// Wrapper for win32 Threads 9 | /// note: handle is owned by this! 10 | struct Thread { 11 | explicit Thread() = default; 12 | explicit Thread(HANDLE handle) 13 | : m_threadHandle{handle} {} 14 | 15 | static auto fromCurrent() -> Thread; 16 | 17 | auto handle() const -> HANDLE { return m_threadHandle.get(); } 18 | 19 | template 20 | void queueUserApc(Functor&& functor) { 21 | auto callback = UniqueCallbackAdapter(std::forward(functor)); 22 | const auto success = ::QueueUserAPC(callback.c_callback(), handle(), callback.c_parameter()); 23 | if (success) callback.release(); 24 | // if (!success) throw Unexpected{"api::setError failed to queue APC"}; 25 | } 26 | 27 | private: 28 | Handle m_threadHandle{}; 29 | }; 30 | 31 | } // namespace win32 32 | -------------------------------------------------------------------------------- /src/MaskedPixelShader.hlsl: -------------------------------------------------------------------------------- 1 | Texture2D bgTexture : register(t0); 2 | Texture2D maskedTexture : register(t1); 3 | SamplerState bgSampler : register(s0); 4 | SamplerState maskedSampler : register(s1); 5 | 6 | struct Input { 7 | float4 position : SV_POSITION; 8 | float2 texCoord : TEXCOORD0; 9 | float2 bgCoord : TEXCOORD1; 10 | }; 11 | 12 | // shadermodel 4 has no bit operations 13 | // normalized float [0..1] xor emulation for 8 bits 14 | float4 fnxor8(float4 l, float4 r) { 15 | float4 o = float4(0, 0, 0, 1); 16 | float4 i = float4(1, 1, 1, 0); 17 | for (int c = 0; c < 8; c++) { 18 | i /= 2; 19 | float4 bl = step(i, l); 20 | float4 br = step(i, r); 21 | l -= bl * i; 22 | r -= br * i; 23 | o += abs(bl - br) * i; 24 | } 25 | return o; 26 | } 27 | 28 | float4 main(Input input) : SV_TARGET{ 29 | float4 mc = maskedTexture.Sample(maskedSampler, input.texCoord); 30 | if (mc.a != 0) { 31 | float4 bg = bgTexture.Sample(bgSampler, input.bgCoord); 32 | return fnxor8(mc, bg); 33 | } 34 | return float4(mc.rgb, 1); 35 | } 36 | -------------------------------------------------------------------------------- /src/CaptureAreaWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Model.h" 3 | 4 | #include "win32/Window.h" 5 | 6 | namespace deskdup { 7 | 8 | using win32::WindowMessageHandler; 9 | using win32::Rect; 10 | using win32::Window; 11 | using win32::WindowClass; 12 | 13 | /// top-most transparent window that marks the captured area of the screen 14 | struct CaptureAreaWindow final : private WindowMessageHandler { 15 | struct Args { 16 | WindowClass const &windowClass; 17 | Rect rect{}; 18 | }; 19 | explicit CaptureAreaWindow(Args const &); 20 | 21 | auto window() -> Window & { return m_window; } 22 | 23 | void updateIsShown(bool isShown); 24 | void updateDuplicationStatus(DuplicationStatus); 25 | void updateRect(Rect); 26 | 27 | private: 28 | friend struct WindowMessageHandler; 29 | bool paint() override; 30 | 31 | private: 32 | win32::WindowWithMessages m_window; 33 | Dimension m_dimension; 34 | DuplicationStatus m_duplicationStatus; 35 | }; 36 | 37 | } // namespace deskdup 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Andreas Reischuck 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/RenderThread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "WindowRenderer.h" 3 | #include "win32/Thread.h" 4 | #include "win32/ThreadLoop.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace deskdup { 10 | 11 | struct RenderThread { 12 | using Config = WindowRenderer::Args; 13 | explicit RenderThread(Config const &); 14 | ~RenderThread(); 15 | 16 | auto thread() -> win32::Thread & { return m_thread; } 17 | auto threadLoop() -> win32::ThreadLoop & { return m_threadLoop; } 18 | auto windowRenderer() -> WindowRenderer & { return m_windowRenderer; } 19 | 20 | void start(); 21 | void stop(); 22 | 23 | void reset(); 24 | void renderFrame(); 25 | void updated(); 26 | 27 | private: 28 | void installNextFrame(); 29 | void uninstallNextFrame(); 30 | 31 | auto nextFrame(HANDLE) -> win32::ThreadLoop::Keep; 32 | 33 | private: 34 | win32::Thread m_thread{}; 35 | win32::ThreadLoop m_threadLoop{}; 36 | 37 | WindowRenderer m_windowRenderer; 38 | 39 | bool m_hasFrameRendered{}; 40 | bool m_isDirty{}; 41 | bool m_hasFrameHandle{}; 42 | Handle m_waitFrameHandle{}; 43 | 44 | std::optional m_stdThread; 45 | }; 46 | 47 | } // namespace deskdup 48 | -------------------------------------------------------------------------------- /src/PointerUpdater.cpp: -------------------------------------------------------------------------------- 1 | #include "PointerUpdater.h" 2 | 3 | #include "CapturedUpdate.h" 4 | #include "FrameContext.h" 5 | 6 | #include 7 | 8 | void PointerUpdater::update(PointerUpdate &update, const FrameContext &context) { 9 | if (update.update_time == 0) return; 10 | if (m_pointer_desktop == context.output_desc.DesktopCoordinates // 11 | || (update.position.Visible && 12 | (!m_pointer.visible || update.update_time > m_pointer.position_timestamp))) { 13 | m_pointer_desktop = context.output_desc.DesktopCoordinates; 14 | m_pointer.position_timestamp = update.update_time; 15 | m_pointer.visible = update.position.Visible; 16 | m_pointer.position.x = update.position.Position.x + 17 | context.output_desc.DesktopCoordinates.left - context.offset.x; 18 | m_pointer.position.y = update.position.Position.y + 19 | context.output_desc.DesktopCoordinates.top - context.offset.y; 20 | } 21 | if (!update.shape_buffer.empty()) { 22 | m_pointer.shape_timestamp = update.update_time; 23 | m_pointer.shape_data = std::move(update.shape_buffer); 24 | m_pointer.shape_info = update.shape_info; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | ColumnLimit: 120 5 | IndentWidth: 4 6 | AccessModifierOffset: -4 7 | 8 | AllowShortBlocksOnASingleLine: false 9 | AllowShortFunctionsOnASingleLine: true 10 | AllowShortIfStatementsOnASingleLine: true 11 | AllowShortLoopsOnASingleLine: true 12 | AllowShortCaseLabelsOnASingleLine: true 13 | 14 | AlignAfterOpenBracket: AlwaysBreak 15 | AlignTrailingComments: false 16 | 17 | AlwaysBreakTemplateDeclarations: true 18 | AlwaysBreakBeforeMultilineStrings: true 19 | BreakConstructorInitializers: BeforeComma 20 | 21 | BreakBeforeBraces: Custom 22 | BraceWrapping: 23 | AfterNamespace: false 24 | # AfterExternBlock: false 25 | AfterClass: false 26 | AfterStruct: false 27 | AfterUnion: false 28 | AfterEnum: false 29 | AfterFunction: false 30 | AfterControlStatement: false 31 | BeforeCatch: true 32 | BeforeElse: true 33 | IndentBraces: false 34 | SplitEmptyFunction: false 35 | SplitEmptyRecord: false 36 | SplitEmptyNamespace: true 37 | 38 | BinPackArguments: false 39 | BinPackParameters: false 40 | 41 | SpaceAfterTemplateKeyword: false 42 | SpaceBeforeParens: ControlStatements 43 | IndentPPDirectives: AfterHash 44 | 45 | SortIncludes: true 46 | SortUsingDeclarations: true 47 | -------------------------------------------------------------------------------- /src/Model.cpp: -------------------------------------------------------------------------------- 1 | #include "Model.h" 2 | 3 | #include 4 | 5 | namespace deskdup { 6 | 7 | auto State::computeIndexForMonitorHandle(HMONITOR monitor) -> MonitorIndex { 8 | auto it = std::find_if(monitors.cbegin(), monitors.cend(), [=](Monitor const &m) { return m.handle == monitor; }); 9 | if (it == monitors.cend()) return 0; 10 | return static_cast(it - monitors.cbegin()); 11 | } 12 | 13 | auto PresentMirrorLens::captureAreaRect(State const &m) -> Rect { 14 | auto dim = m.config.outputDimension; 15 | auto zoom = m.config.outputZoom; 16 | auto offset = m.config.captureOffset; 17 | auto topLeft = m.captureMonitorRect.topLeft; 18 | return Rect{ 19 | Point{ 20 | static_cast(topLeft.x - offset.x - 0.1), 21 | static_cast(topLeft.y - offset.y - 0.1), 22 | }, 23 | Dimension{ 24 | static_cast(1.5 + dim.width / zoom), 25 | static_cast(1.5 + dim.height / zoom), 26 | }, 27 | }; 28 | } 29 | 30 | auto CaptureAreaLens::captureOffset(const State &m) -> Vec2f { 31 | auto topLeft = m.captureMonitorRect.topLeft; 32 | return Vec2f{ 33 | topLeft.x - static_cast(m.config.outputTopLeft.x), 34 | topLeft.y - static_cast(m.config.outputTopLeft.y), 35 | }; 36 | } 37 | 38 | } // namespace deskdup 39 | -------------------------------------------------------------------------------- /src/FrameUpdater.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "BaseRenderer.h" 3 | 4 | #include 5 | #include 6 | 7 | struct FrameUpdate; 8 | struct FrameContext; 9 | 10 | struct RenderFailure { 11 | RenderFailure(HRESULT, const char *) noexcept {} 12 | }; 13 | 14 | struct FrameUpdater { 15 | struct InitArgs : BaseRenderer::InitArgs { 16 | HANDLE targetHandle{}; // shared texture handle that should be updated 17 | }; 18 | using Vertex = BaseRenderer::Vertex; 19 | using quad_vertices = std::array; 20 | 21 | FrameUpdater(InitArgs &&args); 22 | 23 | void update(const FrameUpdate &data, const FrameContext &context); 24 | 25 | private: 26 | void performMoves(const FrameUpdate &data, const FrameContext &context); 27 | void updateDirty(const FrameUpdate &data, const FrameContext &context); 28 | 29 | private: 30 | struct Resources : BaseRenderer { 31 | Resources(FrameUpdater::InitArgs &&args); 32 | 33 | void prepare(HANDLE targetHandle); 34 | 35 | void activateRenderTarget() { deviceContext()->OMSetRenderTargets(1, renderTarget.GetAddressOf(), nullptr); } 36 | 37 | ComPtr target; 38 | ComPtr renderTarget; 39 | 40 | ComPtr moveTmp; 41 | ComPtr vertexBuffer; 42 | uint32_t vertexBufferSize{}; 43 | }; 44 | Resources m_dx; 45 | }; 46 | -------------------------------------------------------------------------------- /src/TaskbarButtons.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "MainController.h" 3 | #include "Model.h" 4 | 5 | #include "win32/TaskbarList.h" 6 | #include "win32/Window.h" 7 | 8 | namespace deskdup { 9 | 10 | using win32::Window; 11 | 12 | /// manages the buttons in the Taskbar, when hovering 13 | struct TaskbarButtons { 14 | TaskbarButtons(); 15 | 16 | auto userMessage() const -> uint32_t { return m_taskbarCreatedMessage; } 17 | 18 | void createButtons(Window &, const State &state); 19 | 20 | bool handleCommand(int command, MainController &); 21 | 22 | void updateMaximized(bool isMaximized); 23 | void updateSystemStatus(SystemStatus); 24 | void updateDuplicationStatus(DuplicationStatus, PauseRendering); 25 | void updateVisibleArea(bool isShown); 26 | 27 | private: 28 | void visualizeMaximized(bool isMaximized); 29 | void visualizeSystemStatus(SystemStatus); 30 | void visualizeDuplicationStatus(DuplicationStatus, PauseRendering); 31 | void visualizeVisibleArea(bool isShown); 32 | void finalizeButtons(); 33 | 34 | private: 35 | uint32_t m_taskbarCreatedMessage{}; 36 | bool m_hasButtons{}; 37 | win32::TaskbarList m_taskbarList{}; 38 | 39 | bool m_updatedButtons{}; 40 | bool m_showsMaximized{}; 41 | SystemStatus m_showsSystemStatus{}; 42 | DuplicationStatus m_showsDuplicationStatus{}; 43 | bool m_showsVisibleArea{}; 44 | }; 45 | 46 | } // namespace deskdup 47 | -------------------------------------------------------------------------------- /src/win32/DisplayMonitor.cpp: -------------------------------------------------------------------------------- 1 | #include "DisplayMonitor.h" 2 | 3 | namespace win32 { 4 | 5 | auto DisplayMonitor::fromPoint(Point point, DisplayMonitor::Fallback fallback) -> DisplayMonitor { 6 | auto flags = [&]() -> DWORD { 7 | switch (fallback) { 8 | case Fallback::Nearest: return MONITOR_DEFAULTTONEAREST; 9 | case Fallback::None: return MONITOR_DEFAULTTONULL; 10 | case Fallback::Primary: return MONITOR_DEFAULTTOPRIMARY; 11 | } 12 | return MONITOR_DEFAULTTONULL; 13 | }(); 14 | return DisplayMonitor{::MonitorFromPoint(POINT{point.x, point.y}, flags)}; 15 | } 16 | 17 | auto DisplayMonitor::fromRect(Rect rect, Fallback fallback) -> DisplayMonitor { 18 | auto flags = [&]() -> DWORD { 19 | switch (fallback) { 20 | case Fallback::Nearest: return MONITOR_DEFAULTTONEAREST; 21 | case Fallback::None: return MONITOR_DEFAULTTONULL; 22 | case Fallback::Primary: return MONITOR_DEFAULTTOPRIMARY; 23 | } 24 | return MONITOR_DEFAULTTONULL; 25 | }(); 26 | auto winRect = rect.toRECT(); 27 | return DisplayMonitor{::MonitorFromRect(&winRect, flags)}; 28 | } 29 | 30 | auto DisplayMonitor::monitorInfo() const -> MonitorInfo { 31 | auto mi = MONITORINFO{}; 32 | mi.cbSize = sizeof(MONITORINFO); 33 | ::GetMonitorInfoA(m_monitorHandle, &mi); 34 | return MonitorInfo{ 35 | Rect::fromRECT(mi.rcMonitor), 36 | Rect::fromRECT(mi.rcWork), 37 | mi.dwFlags == MONITORINFOF_PRIMARY, 38 | }; 39 | } 40 | 41 | } // namespace win32 42 | -------------------------------------------------------------------------------- /src/CapturedUpdate.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "meta/fromByteSpan.h" 3 | #include "meta/comptr.h" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | using moved_view = std::span; 11 | using dirty_view = std::span; 12 | 13 | struct FrameUpdate { 14 | int64_t present_time{}; 15 | uint32_t frames{}; 16 | bool rects_coalesced{}; 17 | bool protected_content_masked_out{}; 18 | 19 | ComPtr image{}; // texture with the entire display (in display device) 20 | 21 | std::vector buffer{}; // managed buffer with move & dirty data 22 | uint32_t moved_bytes{}; 23 | uint32_t dirty_bytes{}; 24 | 25 | auto moved() const noexcept -> moved_view { 26 | auto moved_span = std::span{buffer.data(), moved_bytes}; 27 | return fromByteSpan(moved_span); 28 | } 29 | auto dirty() const noexcept -> dirty_view { 30 | #pragma warning(suppress : 26481) 31 | auto dirty_span = std::span{buffer.data() + moved_bytes, dirty_bytes}; 32 | return fromByteSpan(dirty_span); 33 | } 34 | }; 35 | 36 | struct PointerUpdate { 37 | uint64_t update_time{}; 38 | DXGI_OUTDUPL_POINTER_POSITION position{}; 39 | 40 | DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info{}; // infos about pointer changes 41 | std::vector shape_buffer{}; // managed buffer for new cursor shape 42 | }; 43 | 44 | struct CapturedUpdate { 45 | FrameUpdate frame{}; 46 | PointerUpdate pointer{}; 47 | }; 48 | -------------------------------------------------------------------------------- /src/win32/WaitableTimer.cpp: -------------------------------------------------------------------------------- 1 | #include "WaitableTimer.h" 2 | 3 | namespace win32 { 4 | 5 | WaitableTimer::WaitableTimer(Config config) 6 | : m_timerName(std::move(config.timerName)) { 7 | auto securityAttributes = nullptr; 8 | auto flags = config.manualReset ? CREATE_WAITABLE_TIMER_MANUAL_RESET : 0u; 9 | m_handle.reset(::CreateWaitableTimerExW(securityAttributes, m_timerName.data(), flags, config.desiredAccess)); 10 | } 11 | 12 | void WaitableTimer::cancel() { ::CancelWaitableTimer(m_handle.get()); } 13 | 14 | bool WaitableTimer::setImpl(SetArgs const &args, PTIMERAPCROUTINE callback, LPVOID callbackPtr) { 15 | auto queueTime = LARGE_INTEGER{}; 16 | std::visit( 17 | [&](const T &v) { 18 | if constexpr (std::is_same_v) { 19 | queueTime.QuadPart = 20 | std::chrono::duration_cast(v.time_since_epoch()).count() + 116444736000000000; 21 | } 22 | else if constexpr (std::is_same_v) { 23 | queueTime.QuadPart = -v.count(); 24 | } 25 | else { 26 | static_assert(sizeof(T) != sizeof(T), "Missing!"); 27 | } 28 | }, 29 | args.time); 30 | auto period = ULONG(args.period ? args.period->count() : 0u); 31 | auto tolerableDelay = static_cast(args.tolerableDelay.count()); 32 | auto success = ::SetWaitableTimerEx(handle(), &queueTime, period, callback, callbackPtr, nullptr, tolerableDelay); 33 | return success != 0; 34 | } 35 | 36 | } // namespace win32 37 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: push 4 | 5 | jobs: 6 | windows-qbs: 7 | name: Build with Qbs 8 | runs-on: windows-2022 9 | 10 | steps: 11 | - name: Install Qbs 12 | run: choco install qbs 13 | 14 | - name: Setup Qbs 15 | run: | 16 | qbs setup-toolchains --detect 17 | qbs config defaultProfile MSVC2022-x64 18 | qbs config --list profiles 19 | 20 | - name: Git Checkout 21 | uses: actions/checkout@v4 22 | 23 | - run: >- 24 | qbs build 25 | --file DesktopDuplicator.qbs 26 | --build-directory ${env:RUNNER_TEMP}\build 27 | qbs.installRoot:${{ github.workspace }}/install-root 28 | config:Release qbs.defaultBuildVariant:release 29 | 30 | - name: Pack 31 | working-directory: ${{ github.workspace }}/install-root 32 | run: 7z a ../DesktopDuplicator-${{ github.run_id }}.7z * -r 33 | 34 | - name: Upload 35 | uses: actions/upload-artifact@v4 36 | with: 37 | path: ./DesktopDuplicator-${{ github.run_id }}.7z 38 | name: DesktopDuplicator-${{ github.run_id }}.7z 39 | 40 | - name: Upload binaries to release 41 | if: contains(github.ref, 'tags/v') 42 | uses: svenstaro/upload-release-action@v2 43 | with: 44 | tag: ${{ github.ref }} 45 | repo_token: ${{ secrets.GITHUB_TOKEN }} 46 | release_name: Release ${{ github.ref }} 47 | overwrite: true 48 | file: ${{ github.workspace }}/DesktopDuplicator-${{ github.run_id }}.7z 49 | -------------------------------------------------------------------------------- /src/renderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "meta/comptr.h" 3 | #include "win32/Geometry.h" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace renderer { 11 | 12 | using win32::Dimension; 13 | using win32::Rect; 14 | 15 | struct Error { 16 | HRESULT result{}; 17 | const char *message{}; 18 | }; 19 | 20 | struct DeviceData { 21 | ComPtr device{}; 22 | ComPtr deviceContext{}; 23 | D3D_FEATURE_LEVEL selectedFeatureLevel{}; 24 | }; 25 | auto createDevice() -> DeviceData; 26 | 27 | auto getFactory(const ComPtr &device) -> ComPtr; 28 | 29 | auto createSwapChain(const ComPtr &factory, const ComPtr &device, HWND window) 30 | -> ComPtr; 31 | 32 | struct DimensionData { 33 | Rect rect{}; 34 | std::vector used_displays{}; 35 | }; 36 | 37 | auto getDimensionData(const ComPtr &device, const std::vector &displays) -> DimensionData; 38 | 39 | auto createTexture(const ComPtr &device, Dimension) -> ComPtr; 40 | auto createSharedTexture(const ComPtr &device, Dimension) -> ComPtr; 41 | 42 | // note: Do N-O-T close this handle! 43 | auto getSharedHandle(const ComPtr &texture) -> HANDLE; 44 | auto getTextureFromHandle(const ComPtr &device, HANDLE handle) -> ComPtr; 45 | 46 | auto renderToTexture(const ComPtr &device, const ComPtr &texture) 47 | -> ComPtr; 48 | 49 | } // namespace renderer 50 | -------------------------------------------------------------------------------- /src/MainController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Model.h" 3 | 4 | #include "win32/Geometry.h" 5 | 6 | namespace deskdup { 7 | 8 | using win32::Dimension; 9 | using win32::Point; 10 | using win32::Rect; 11 | 12 | struct MainController { 13 | MainController() = default; 14 | MainController(MainController const &) = delete; 15 | MainController &operator=(MainController const &) = delete; 16 | virtual ~MainController() = default; 17 | 18 | virtual auto state() const -> State const & = 0; 19 | auto config() const -> Config const { return state().config; } 20 | auto operatonModeLens() const -> OperationModeLens const { return OperationModeLens{state()}; } 21 | 22 | virtual void quit() = 0; 23 | virtual void changeOperationMode(OperationMode) = 0; 24 | virtual void updateSystemStatus(SystemStatus) = 0; 25 | virtual void captureScreen(int) = 0; 26 | virtual void updateScreenRect(Rect) = 0; 27 | virtual void resizeOutputWindow(Dimension, bool isMaximized) = 0; 28 | virtual void moveOutputWindowTo(Point) = 0; 29 | virtual void moveCaptureOffsetByScreen(int dx, int dy) = 0; 30 | virtual void resetOutputZoom() = 0; 31 | virtual void zoomOutputBy(float) = 0; 32 | virtual void zoomOutputAt(Point, float) = 0; 33 | virtual void toggleVisualizeCaptureArea() = 0; 34 | virtual void changeDuplicationStatus(DuplicationStatus status) = 0; 35 | virtual void toggleOutputMaximize() = 0; 36 | virtual void refreshMonitors() = 0; 37 | 38 | void togglePause() { 39 | using enum DuplicationStatus; 40 | changeDuplicationStatus(state().duplicationStatus != Pause ? Pause : Live); 41 | } 42 | }; 43 | 44 | } // namespace deskdup 45 | -------------------------------------------------------------------------------- /src/win32/ThreadLoop.cpp: -------------------------------------------------------------------------------- 1 | #include "ThreadLoop.h" 2 | 3 | namespace win32 { 4 | 5 | void ThreadLoop::sleep() { 6 | auto handleCount = static_cast(m_waitHandles.size()); 7 | auto handleData = m_waitHandles.data(); 8 | auto wakeMask = m_queueStatus; 9 | auto flags = (m_awaitAlerts ? MWMO_ALERTABLE : 0u) | (m_queueStatus != 0 ? MWMO_INPUTAVAILABLE : 0u); 10 | 11 | auto timeout = static_cast(m_timeout.count()); 12 | auto awoken = ::MsgWaitForMultipleObjectsEx(handleCount, handleData, timeout, wakeMask, flags); 13 | if (awoken >= WAIT_OBJECT_0 && awoken < WAIT_OBJECT_0 + handleCount) { 14 | auto i = awoken - WAIT_OBJECT_0; 15 | auto keep = m_callbacks[i](m_waitHandles[i]); 16 | if (keep == Keep::Remove) { 17 | m_callbacks.erase(m_callbacks.begin() + i); 18 | m_waitHandles.erase(m_waitHandles.begin() + i); 19 | } 20 | } 21 | else if (awoken == WAIT_OBJECT_0 + handleCount) { 22 | processCurrentMessages(); 23 | } 24 | else if (awoken == WAIT_IO_COMPLETION) { 25 | // APC - already called 26 | } 27 | else { 28 | OutputDebugStringA("Unhandled Awoken\n"); 29 | } 30 | } 31 | 32 | void ThreadLoop::processCurrentMessages() { 33 | while (true) { 34 | auto msg = MSG{}; 35 | const auto success = ::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE); 36 | if (0 == success) { 37 | return; 38 | } 39 | ::TranslateMessage(&msg); 40 | ::DispatchMessage(&msg); 41 | if (WM_QUIT == msg.message) { 42 | m_quit = true; 43 | m_returnValue = static_cast(msg.wParam); 44 | } 45 | } 46 | } 47 | 48 | } // namespace win32 49 | -------------------------------------------------------------------------------- /src/meta/member_method.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | template 5 | constexpr auto is_member_method = false; 6 | 7 | template 8 | constexpr auto is_member_method = true; 9 | 10 | template 11 | concept MemberMethod = is_member_method; 12 | 13 | template 14 | constexpr auto is_member_method_sig = false; 15 | 16 | template 17 | constexpr auto is_member_method_sig = true; 18 | 19 | template 20 | concept MemberMedthodSig = is_member_method_sig; 21 | 22 | template 23 | auto memberMethodClass(Ret (C::*)(Args...)) -> C; 24 | 25 | template 26 | using MemberMethodClass = decltype(memberMethodClass(M)); 27 | 28 | template 29 | auto memberMethodArgsTuple(Ret (C::*)(Args...)) -> std::tuple...>; 30 | 31 | template 32 | using MemberMethodArgsTuple = decltype(memberMethodArgsTuple(M)); 33 | 34 | template 35 | auto memberMethodClassArgsTuple(Ret (C::*)(Args...)) 36 | -> std::tuple...>; 37 | 38 | template 39 | using MemberMethodClassArgsTuple = decltype(memberMethodClassArgsTuple(M)); 40 | 41 | // #include 42 | 43 | // struct TC { 44 | // constexpr int memberMethod(int x) { return x + 5; } 45 | // }; 46 | 47 | // static_assert(std::is_same_v, TC>); 48 | 49 | // template 50 | // constexpr auto memberCall(TC p, int x) { 51 | // return (p.*M)(x); 52 | // } 53 | 54 | // static_assert(memberCall<&TC::memberMethod>(TC{}, 3) == 8); 55 | -------------------------------------------------------------------------------- /src/MainApplication.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CaptureAreaWindow.h" 3 | #include "DuplicationController.h" 4 | #include "MainController.h" 5 | #include "MainThread.h" 6 | #include "Model.h" 7 | #include "OutputWindow.h" 8 | 9 | #include "win32/PowerRequest.h" 10 | 11 | #include 12 | 13 | namespace deskdup { 14 | 15 | struct MainApplication final : private MainController { 16 | explicit MainApplication(HINSTANCE instanceHandle); 17 | 18 | int run(); 19 | 20 | // MainController interface 21 | private: 22 | auto state() const -> State const & override { return m_state; } 23 | 24 | void quit() override; 25 | void changeOperationMode(OperationMode) override; 26 | void updateSystemStatus(SystemStatus) override; 27 | void updateScreenRect(Rect) override; 28 | void captureScreen(int) override; 29 | void resizeOutputWindow(Dimension, bool isMaximized) override; 30 | void moveOutputWindowTo(Point) override; 31 | void moveCaptureOffsetByScreen(int dx, int dy) override; 32 | void resetOutputZoom() override; 33 | void zoomOutputBy(float) override; 34 | void zoomOutputAt(Point, float) override; 35 | void toggleVisualizeCaptureArea() override; 36 | void changeDuplicationStatus(DuplicationStatus) override; 37 | void toggleOutputMaximize() override; 38 | void refreshMonitors() override; 39 | 40 | private: 41 | bool updateCaptureAreaOutputScreen(); 42 | void updateOutputRect(Rect); 43 | void resetMonitors(); 44 | 45 | private: 46 | State m_state{}; 47 | MainThread m_mainThread; 48 | 49 | std::optional m_outputWindow{}; 50 | std::optional m_captureAreaWindow{}; 51 | std::optional m_duplicationController{}; 52 | 53 | using PowerRequest = win32::PowerRequest; 54 | PowerRequest m_powerRequest; 55 | bool m_hasPowerRequest{}; 56 | }; 57 | 58 | } // namespace deskdup 59 | -------------------------------------------------------------------------------- /src/win32/DisplayMonitor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Geometry.h" 3 | 4 | #include 5 | 6 | namespace win32 { 7 | 8 | struct MonitorInfo { 9 | Rect monitorRect{}; 10 | Rect workRect{}; 11 | bool isPrimary{}; 12 | 13 | bool operator==(MonitorInfo const &) const = default; 14 | }; 15 | 16 | /// Wrapper around a display/monitor in Windows 17 | struct DisplayMonitor { 18 | enum Fallback { 19 | Nearest, 20 | None, 21 | Primary, 22 | }; 23 | 24 | explicit DisplayMonitor() = default; 25 | explicit DisplayMonitor(HMONITOR hMonitor) 26 | : m_monitorHandle(hMonitor) {} 27 | 28 | /// construct a DisplayMonitor for a given virtual screen point 29 | static auto fromPoint(Point, Fallback = Fallback::Nearest) -> DisplayMonitor; 30 | 31 | /// construct a DisplayMonitor for a given virtual screen rect 32 | static auto fromRect(Rect, Fallback = Fallback::Nearest) -> DisplayMonitor; 33 | 34 | /// fall given f for each DisplayMonitor in the system 35 | template 36 | requires requires(F f, DisplayMonitor dm) { f(dm, Rect{}); } 37 | static void enumerateAll(F &&f) { 38 | struct Callback { 39 | static auto CALLBACK enumerateCallback(HMONITOR hMonitor, HDC, LPRECT rect, LPARAM lParam) -> BOOL { 40 | auto *fp = std::bit_cast(lParam); 41 | (*fp)(DisplayMonitor{hMonitor}, Rect::fromRECT(*rect)); 42 | return true; 43 | } 44 | }; 45 | ::EnumDisplayMonitors(nullptr, nullptr, &Callback::enumerateCallback, std::bit_cast(&f)); 46 | } 47 | 48 | /// true if handle is valid 49 | bool isValid() const { return m_monitorHandle != HMONITOR{}; } 50 | 51 | /// handle to call other win32 APIs 52 | auto handle() const -> HMONITOR { return m_monitorHandle; } 53 | 54 | /// retrieves MonitorInfo 55 | auto monitorInfo() const -> MonitorInfo; 56 | 57 | private: 58 | HMONITOR m_monitorHandle{}; 59 | }; 60 | 61 | } // namespace win32 62 | -------------------------------------------------------------------------------- /src/win32/PowerRequest.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace win32 { 6 | 7 | namespace { 8 | 9 | auto createPowerRequestHandle(const wchar_t *reasonString) noexcept -> HANDLE { 10 | REASON_CONTEXT reason; 11 | reason.Version = POWER_REQUEST_CONTEXT_VERSION; 12 | reason.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING; 13 | [[gsl::suppress("26492")]] // the C API is not const correct! 14 | reason.Reason.SimpleReasonString = const_cast(reasonString); 15 | return PowerCreateRequest(&reason); 16 | } 17 | 18 | } // namespace 19 | 20 | template 21 | struct PowerRequest { 22 | PowerRequest() = default; 23 | PowerRequest(const wchar_t *reason) noexcept 24 | : m_handle(createPowerRequestHandle(reason)) {} 25 | ~PowerRequest() { 26 | if (m_was_set) clear(); 27 | CloseHandle(m_handle); 28 | } 29 | 30 | PowerRequest(const PowerRequest &) = delete; 31 | auto operator=(const PowerRequest &) -> PowerRequest & = delete; 32 | PowerRequest(PowerRequest &&o) noexcept 33 | : m_was_set(o.m_was_set) 34 | , m_handle(o.m_handle) { 35 | o.reset(); 36 | } 37 | auto operator=(PowerRequest &&o) noexcept -> PowerRequest & { 38 | if (m_was_set) clear(); 39 | CloseHandle(m_handle); 40 | m_handle = o.m_handle; 41 | m_was_set = o.m_was_set; 42 | o.reset(); 43 | return *this; 44 | } 45 | 46 | bool set() noexcept { 47 | if (m_was_set) return true; 48 | m_was_set = true; 49 | return (PowerSetRequest(m_handle, required) && ...); 50 | } 51 | 52 | bool clear() noexcept { 53 | if (!m_was_set) return true; 54 | m_was_set = false; 55 | return (PowerClearRequest(m_handle, required) && ...); 56 | } 57 | 58 | private: 59 | void reset() noexcept { 60 | m_was_set = false; 61 | m_handle = INVALID_HANDLE_VALUE; 62 | } 63 | 64 | private: 65 | bool m_was_set = false; 66 | HANDLE m_handle = INVALID_HANDLE_VALUE; 67 | }; 68 | 69 | } // namespace win32 70 | -------------------------------------------------------------------------------- /src/RenderThread.cpp: -------------------------------------------------------------------------------- 1 | #include "RenderThread.h" 2 | 3 | namespace deskdup { 4 | 5 | RenderThread::RenderThread(Config const &config) 6 | : m_windowRenderer{config} {} 7 | 8 | RenderThread::~RenderThread() { 9 | if (m_stdThread) stop(); 10 | } 11 | 12 | void RenderThread::start() { 13 | if (m_stdThread) return; // already started 14 | m_threadLoop.enableAwaitAlerts(); 15 | m_stdThread.emplace([this] { 16 | m_thread = win32::Thread::fromCurrent(); 17 | m_threadLoop.run(); 18 | }); 19 | } 20 | 21 | void RenderThread::stop() { 22 | m_thread.queueUserApc([this]() { m_threadLoop.quit(); }); 23 | m_stdThread.reset(); 24 | } 25 | 26 | void RenderThread::reset() { 27 | m_thread.queueUserApc([this]() { 28 | if (m_hasFrameHandle) { 29 | uninstallNextFrame(); 30 | } 31 | m_windowRenderer.reset(); 32 | m_hasFrameRendered = false; 33 | m_isDirty = false; 34 | }); 35 | } 36 | 37 | void RenderThread::renderFrame() { 38 | if (m_hasFrameRendered) { 39 | m_isDirty = true; 40 | return; 41 | } 42 | m_windowRenderer.render(); 43 | m_hasFrameRendered = true; 44 | m_isDirty = false; 45 | if (!m_hasFrameHandle) { 46 | installNextFrame(); 47 | } 48 | } 49 | 50 | void RenderThread::updated() { 51 | if (!m_windowRenderer.isInitialized()) return; 52 | m_isDirty = true; 53 | if (!m_hasFrameHandle) { 54 | installNextFrame(); 55 | } 56 | } 57 | 58 | void RenderThread::installNextFrame() { 59 | m_waitFrameHandle = m_windowRenderer.frameLatencyWaitable(); 60 | m_threadLoop.addAwaitableMember<&RenderThread::nextFrame>(m_waitFrameHandle.get(), this); 61 | m_hasFrameHandle = true; 62 | } 63 | 64 | void RenderThread::uninstallNextFrame() { 65 | m_threadLoop.removeAwaitable(m_waitFrameHandle.get()); 66 | m_waitFrameHandle.reset(); 67 | m_hasFrameHandle = false; 68 | } 69 | 70 | auto RenderThread::nextFrame(HANDLE) -> win32::ThreadLoop::Keep { 71 | if (m_isDirty) { 72 | m_windowRenderer.render(); 73 | m_isDirty = false; 74 | m_hasFrameRendered = true; 75 | } 76 | else if (m_hasFrameRendered) { 77 | m_hasFrameRendered = false; 78 | } 79 | else { 80 | uninstallNextFrame(); 81 | } 82 | return win32::ThreadLoop::Keep::Yes; 83 | } 84 | 85 | } // namespace deskdup 86 | -------------------------------------------------------------------------------- /src/win32/TaskbarList.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "meta/comptr.h" 3 | #include "meta/flags.h" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace win32 { 11 | 12 | enum class ProgressFlag { 13 | Disabled = 0, 14 | Indeterminate = 1, 15 | Normal = 2, 16 | Error = 4, 17 | Paused = 8, 18 | }; 19 | using ProgressFlags = meta::flags; 20 | META_FLAGS_OP(ProgressFlag) 21 | 22 | enum class ThumbButtonFlag { 23 | Enabled = 0, 24 | Disabled = 1 << 0, 25 | DismissOnClick = 1 << 1, 26 | NoBackground = 1 << 2, 27 | Hidden = 1 << 3, 28 | NonInteractive = 1 << 4, 29 | }; 30 | using ThumbButtonFlags = meta::flags; 31 | META_FLAGS_OP(ThumbButtonFlag) 32 | 33 | struct MenuEntry { 34 | HICON icon{}; 35 | HBITMAP bitmap{}; 36 | std::wstring tooltip; 37 | ThumbButtonFlags flags{ThumbButtonFlag::Hidden}; 38 | 39 | MenuEntry() = default; 40 | MenuEntry(const MenuEntry &o); 41 | auto operator=(const MenuEntry &) -> MenuEntry &; 42 | MenuEntry(MenuEntry &&o) = default; 43 | auto operator=(MenuEntry &&o) -> MenuEntry & = default; 44 | ~MenuEntry() = default; 45 | }; 46 | using menu_entries = std::array; 47 | 48 | struct TaskbarList { 49 | struct Config { 50 | UINT idBase = 0u; 51 | int iconSize = 24; 52 | }; 53 | 54 | TaskbarList() = default; 55 | TaskbarList(HWND window) 56 | : TaskbarList(window, Config{}) {} 57 | TaskbarList(HWND window, Config); 58 | 59 | auto forWindow(HWND window) const noexcept -> TaskbarList; 60 | 61 | void setProgressFlags(ProgressFlags = ProgressFlag::Disabled); 62 | void setProgressValue(uint64_t value, uint64_t total = 100); 63 | 64 | void setButtonLetterIcon(size_t idx, wchar_t chr, COLORREF Color = RGB(255, 255, 255)) noexcept; 65 | void setButtonTooltip(size_t idx, std::wstring = {}); 66 | void setButtonFlags(size_t idx, ThumbButtonFlags = ThumbButtonFlag::Hidden) noexcept; 67 | 68 | void updateThumbButtons(); 69 | 70 | private: 71 | void updateThumbImages(); 72 | 73 | private: 74 | HWND m_window{}; 75 | UINT m_idBase = 1; 76 | int m_iconSize = 24; 77 | ComPtr p{}; 78 | menu_entries m_menu{}; 79 | bool m_menuInitialized = false; 80 | bool m_imageUpdated = false; 81 | }; 82 | 83 | } // namespace win32 84 | -------------------------------------------------------------------------------- /src/CaptureAreaWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "CaptureAreaWindow.h" 2 | 3 | namespace deskdup { 4 | namespace { 5 | 6 | using win32::WindowWithMessages; 7 | 8 | constexpr static auto colorKey = RGB(0xFF, 0x20, 0xFF); 9 | 10 | auto borderRect(Rect rect) -> Rect { 11 | return Rect{ 12 | Point{rect.left() - 2, rect.top() - 2}, 13 | Dimension{rect.width() + 4, rect.height() + 4}, 14 | }; 15 | } 16 | 17 | auto createWindow(const WindowClass &windowClass, Rect rect) -> WindowWithMessages { 18 | return windowClass.createWindow({ 19 | .style = win32::WindowStyle::popup().topMost().transparent(), 20 | .name = win32::Name{}, 21 | .rect = borderRect(rect), 22 | }); 23 | } 24 | 25 | } // namespace 26 | 27 | CaptureAreaWindow::CaptureAreaWindow(const Args &args) 28 | : m_window{createWindow(args.windowClass, args.rect)} 29 | , m_dimension{args.rect.dimension} { 30 | m_window.setMessageHandler(this); 31 | m_window.styleLayeredColorKey(colorKey); 32 | } 33 | 34 | void CaptureAreaWindow::updateIsShown(bool isShown) { 35 | if (isShown) { 36 | m_window.show(); 37 | m_window.update(); 38 | } 39 | else { 40 | m_window.hide(); 41 | } 42 | } 43 | 44 | void CaptureAreaWindow::updateDuplicationStatus(DuplicationStatus status) { 45 | m_duplicationStatus = status; 46 | auto rect = m_window.rect(); 47 | rect.dimension.width += 1; 48 | m_window.move(rect); 49 | rect.dimension.width -= 1; 50 | m_window.move(rect); 51 | } 52 | 53 | void CaptureAreaWindow::updateRect(Rect rect) { 54 | m_dimension = rect.dimension; 55 | m_window.move(borderRect(rect)); 56 | } 57 | 58 | bool CaptureAreaWindow::paint() { 59 | const auto dim = m_dimension; 60 | 61 | auto p = PAINTSTRUCT{}; 62 | auto dc = BeginPaint(m_window.handle(), &p); 63 | 64 | SelectObject(dc, GetStockObject(HOLLOW_BRUSH)); 65 | SelectObject(dc, GetStockObject(DC_PEN)); 66 | 67 | SetDCPenColor(dc, m_duplicationStatus == DuplicationStatus::Live ? RGB(255, 80, 40) : RGB(255, 220, 120)); 68 | Rectangle(dc, 0, 0, dim.width + 4, dim.height + 4); 69 | 70 | SelectObject(dc, GetStockObject(DC_BRUSH)); 71 | SetDCBrushColor(dc, colorKey); 72 | SetDCPenColor(dc, RGB(255, 255, 255)); 73 | Rectangle(dc, 1, 1, dim.width + 3, dim.height + 3); 74 | 75 | EndPaint(m_window.handle(), &p); 76 | return true; 77 | } 78 | 79 | } // namespace deskdup 80 | -------------------------------------------------------------------------------- /src/win32/WaitableTimer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Handle.h" 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace win32 { 15 | 16 | using Name = std::wstring; 17 | using Milliseconds = std::chrono::milliseconds; 18 | using HundredNanoSeconds = std::chrono::duration>; 19 | using TimePoint = std::chrono::system_clock::time_point; 20 | 21 | /// Wrapper for a win32 WaitableTimer handle 22 | struct WaitableTimer { 23 | struct Config { 24 | LPSECURITY_ATTRIBUTES securityAttributes = {}; 25 | Name timerName = {}; 26 | bool manualReset = {}; 27 | DWORD desiredAccess = {TIMER_ALL_ACCESS}; 28 | }; 29 | WaitableTimer(Config); 30 | 31 | auto handle() -> HANDLE { return m_handle.get(); } 32 | 33 | struct SetArgs { 34 | std::variant time = {}; 35 | std::optional period = {}; 36 | // WakeContext?? 37 | Milliseconds tolerableDelay = {}; 38 | }; 39 | template 40 | bool set(SetArgs const &setArgs, MemberMethodClassArgsTuple &&args) { 41 | using ArgsTuple = MemberMethodClassArgsTuple; 42 | struct Helper { 43 | static void CALLBACK apc(LPVOID parameter, DWORD /*dwTimerLowValue*/, DWORD /*dwTimerHighValue*/) noexcept { 44 | auto tuplePtr = std::bit_cast(parameter); 45 | std::apply(M, *tuplePtr); 46 | } 47 | static void freeArgs(void *parameter) { 48 | auto tuplePtr = std::unique_ptr(std::bit_cast(parameter)); 49 | (void)tuplePtr; 50 | } 51 | }; 52 | m_args = UniqueArgPtr{new ArgsTuple(std::move(args)), Deleter{&Helper::freeArgs}}; 53 | return setImpl(setArgs, &Helper::apc, m_args.get()); 54 | } 55 | 56 | void cancel(); 57 | 58 | private: 59 | bool setImpl(SetArgs const &, PTIMERAPCROUTINE, LPVOID); 60 | 61 | private: 62 | Handle m_handle; 63 | Name m_timerName{}; 64 | using DeleteFunc = void(void *); 65 | struct Deleter { 66 | DeleteFunc *func{}; 67 | void operator()(void *ptr) const { func(ptr); } 68 | }; 69 | using UniqueArgPtr = std::unique_ptr; 70 | UniqueArgPtr m_args; 71 | }; 72 | 73 | } // namespace win32 74 | -------------------------------------------------------------------------------- /src/win32/Geometry.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace win32 { 5 | 6 | /// win32 POINT replacement 7 | struct Point { 8 | int x{}; 9 | int y{}; 10 | 11 | constexpr bool operator==(const Point &) const = default; 12 | 13 | /// construct Point from win32 POINT 14 | static constexpr auto fromPOINT(POINT point) -> Point { return {point.x, point.y}; } 15 | 16 | /// create a win32 POINT from this 17 | constexpr auto toPOINT() const -> POINT { return {x, y}; } 18 | }; 19 | 20 | /// 2D vector of floating point coordinates 21 | /// note: used for DirectX 22 | struct Vec2f { 23 | float x{}; 24 | float y{}; 25 | }; 26 | 27 | /// win32 SIZE replacement 28 | struct Dimension { 29 | int width{}; 30 | int height{}; 31 | 32 | constexpr bool operator==(const Dimension &) const = default; 33 | 34 | static constexpr auto fromSIZE(SIZE size) -> Dimension { return {size.cx, size.cy}; } 35 | 36 | constexpr auto toSIZE() const -> SIZE { return {width, height}; } 37 | }; 38 | 39 | /// win32 RECT replacement 40 | /// note: 41 | /// * stores topLeft & dimensions instead of topLeft and bottomRight 42 | /// (this avoids possible confusion of the two points) 43 | struct Rect { 44 | Point topLeft{}; 45 | Dimension dimension{}; 46 | 47 | constexpr bool operator==(const Rect &) const = default; 48 | 49 | /// construct from win32 RECT 50 | static constexpr auto fromRECT(RECT rect) -> Rect { 51 | return { 52 | Point{rect.left, rect.top}, 53 | Dimension{rect.right - rect.left, rect.bottom - rect.top}, 54 | }; 55 | } 56 | /// construct from two win32 POINTs 57 | static constexpr auto fromPOINTS(POINT min, POINT max) -> Rect { 58 | return { 59 | Point{min.x, min.y}, 60 | Dimension{max.x - min.x, max.y - min.y}, 61 | }; 62 | } 63 | 64 | constexpr auto contains(Point p) const -> bool { 65 | return left() <= p.x && right() > p.x && top() <= p.y && bottom() > p.y; 66 | } 67 | 68 | // convinience accessors 69 | constexpr auto left() const -> int { return topLeft.x; } 70 | constexpr auto top() const -> int { return topLeft.y; } 71 | constexpr auto width() const -> int { return dimension.width; } 72 | constexpr auto height() const -> int { return dimension.height; } 73 | 74 | constexpr auto bottom() const -> int { return top() + height(); } 75 | constexpr auto right() const -> int { return left() + width(); } 76 | 77 | /// accessor for the bottomRight point 78 | constexpr auto bottomRight() const -> Point { return {right(), bottom()}; } 79 | 80 | /// create win32 RECT from this 81 | constexpr auto toRECT() const -> RECT { return {left(), top(), right(), bottom()}; } 82 | }; 83 | 84 | } // namespace win32 85 | -------------------------------------------------------------------------------- /src/OutputWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "MainController.h" 3 | #include "Model.h" 4 | #include "TaskbarButtons.h" 5 | 6 | #include "win32/Window.h" 7 | 8 | #include 9 | 10 | namespace deskdup { 11 | 12 | using win32::OptLRESULT; 13 | using win32::Window; 14 | using win32::WindowClass; 15 | using win32::WindowMessageHandler; 16 | using win32::WindowWithMessages; 17 | 18 | /// Holder of the duplication output window 19 | /// note: 20 | /// * handles all messages 21 | /// * reacts to model changes 22 | struct OutputWindow final : private WindowMessageHandler { 23 | struct Args { 24 | WindowClass const &windowClass; 25 | MainController &mainController; 26 | }; 27 | OutputWindow(Args const &); 28 | 29 | auto window() -> WindowWithMessages & { return m_window; } 30 | auto taskbarButtons() -> TaskbarButtons & { return m_taskbarButtons; } 31 | 32 | // used after creation 33 | void show(bool isMaximized); 34 | 35 | void updateRect(Rect); 36 | void updateMaximized(bool isMaximized); 37 | void updateOperationMode(OperationMode); 38 | void updateDuplicationStatus(DuplicationStatus, PauseRendering); 39 | 40 | private: 41 | friend struct WindowMessageHandler; 42 | void size(const Dimension &, uint32_t) override; 43 | void move(const Point &topLeft) override; 44 | bool position(const win32::WindowPosition &) override; 45 | 46 | void close() override; 47 | 48 | std::optional m_draggingLastPosition{}; 49 | void mouseMove(const Point &mousePosition, DWORD keyState) override; 50 | void mouseLeftButtonDown(const Point &mousePosition, DWORD keyState) override; 51 | void mouseLeftButtonUp(const Point &mousePosition, DWORD keyState) override; 52 | 53 | bool mouseRightButtonUp(const Point &mousePosition, DWORD keyState) override; 54 | 55 | void contextMenu(const Point &position) override; 56 | auto menuCommand(uint16_t command) -> OptLRESULT override; 57 | 58 | bool m_pickWindow{}; 59 | void mouseRightDoubleClick(const Point &mousePosition, DWORD keyState) override; 60 | void setFocus() override; 61 | void killFocus() override; 62 | 63 | bool inputKeyDown(uint32_t keyCode) override; 64 | 65 | void mouseLeftButtonDoubleClick(const Point &mousePosition, DWORD keyState) override; 66 | 67 | void mouseWheel(int wheelDelta, const Point &mousePosition, DWORD keyState) override; 68 | 69 | void displayChange(uint32_t bitsPerPixel, Dimension) override; 70 | void dpiChanged(const Rect &rect) override; 71 | 72 | auto userMessage(const win32::WindowMessage &) -> OptLRESULT override; 73 | auto controlCommand(const win32::ControlCommand &) -> OptLRESULT override; 74 | 75 | private: 76 | MainController &m_controller; 77 | WindowWithMessages m_window; 78 | TaskbarButtons m_taskbarButtons; 79 | }; 80 | 81 | } // namespace deskdup 82 | -------------------------------------------------------------------------------- /qbs/modules/hlsl/hlsl.qbs: -------------------------------------------------------------------------------- 1 | import qbs 1.0 2 | import qbs.File 3 | import qbs.FileInfo 4 | import qbs.WindowsUtils 5 | import qbs.ModUtils 6 | import qbs.Utilities 7 | 8 | Module { 9 | property string hlslName: "fxc.exe" 10 | property string hlslPath: hlslName 11 | property string outputDir: "hlsl" 12 | 13 | property stringList flags 14 | PropertyOptions { 15 | name: "flags" 16 | description: "additional flags" 17 | } 18 | 19 | property string entryPoint: 'main' 20 | PropertyOptions { 21 | name: "entryPoint" 22 | description: "name of entry point function" 23 | } 24 | 25 | property string shaderType // ps / vs / ... 26 | PropertyOptions { 27 | name: "shaderType" 28 | description: "type of shader to be compiled" 29 | } 30 | 31 | property string shaderModel // 1_0 / 2_0 / ... 32 | PropertyOptions { 33 | name: "shaderModel" 34 | description: "model level of the shader" 35 | } 36 | 37 | property string outVariableName: "g_*" 38 | PropertyOptions { 39 | name: "outVariableName" 40 | description: "name of the output variable" 41 | } 42 | 43 | Depends { name: "cpp" } 44 | cpp.includePaths: product.buildDirectory + '/' + outputDir 45 | 46 | FileTagger { 47 | patterns: "*.hlsl" 48 | fileTags: ["hlsl"] 49 | } 50 | 51 | Rule { 52 | inputs: ["hlsl"] 53 | Artifact { 54 | filePath: ModUtils.moduleProperty(product, "outputDir") + '/' + input.completeBaseName + ".h" 55 | fileTags: ["hpp"] 56 | } 57 | prepare: { 58 | var args = ["/nologo", 59 | '/E'+ModUtils.moduleProperty(input, 'entryPoint', 'hlsl'), 60 | '/Vn'+ModUtils.moduleProperty(input, 'outVariableName', 'hlsl').replace('*',input.completeBaseName), 61 | '/T'+ModUtils.moduleProperty(input, 'shaderType', 'hlsl') + '_' + ModUtils.moduleProperty(input, 'shaderModel', 'hlsl'), 62 | "/Fh" + FileInfo.toWindowsSeparators(output.filePath)]; 63 | 64 | if (ModUtils.moduleProperty(product, "debugInformation")) { 65 | args.push("/Zi"); 66 | args.push("/Od"); 67 | } 68 | args = args.concat(ModUtils.moduleProperty(input, 'flags', 'hlsl')); 69 | args.push(FileInfo.toWindowsSeparators(input.filePath)); 70 | var cmd = new Command(ModUtils.moduleProperty(product, "hlslPath"), args); 71 | cmd.description = "compiling shader " + input.fileName; 72 | cmd.inputFileName = input.fileName; 73 | cmd.stdoutFilterFunction = function(output) { 74 | var lines = output.split("\r\n").filter(function (s) { 75 | return !s.endsWith(inputFileName); }); 76 | return lines.join("\r\n"); 77 | }; 78 | return cmd; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/meta/callback_adapter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | template 7 | class UniqueCallbackAdapter final { 8 | static_assert(sizeof(Parameter) >= sizeof(void *)); 9 | static_assert(alignof(Parameter) >= alignof(void *)); 10 | using ParameterFunction = void NTAPI(Parameter); 11 | using Deleter = ParameterFunction *; 12 | using Invoker = ParameterFunction *; 13 | static void NTAPI noopParameterFunction(Parameter) {} 14 | 15 | Parameter m_param = nullptr; 16 | Deleter m_deleter = &UniqueCallbackAdapter::noopParameterFunction; 17 | Invoker m_invoker = &UniqueCallbackAdapter::noopParameterFunction; 18 | 19 | template 20 | struct Helper { 21 | static constexpr auto is_small = (sizeof(Functor) <= sizeof(Parameter)); 22 | 23 | static auto param(Functor &&functor) -> Parameter { 24 | if constexpr (is_small) { 25 | auto result = Parameter{}; 26 | new (&result) Functor(std::forward(functor)); 27 | return result; 28 | } 29 | else 30 | return std::bit_cast(std::make_unique(std::forward(functor)).release()); 31 | } 32 | static void NTAPI deleter(Parameter param) { 33 | if constexpr (is_small) { 34 | auto &functor = *std::launder(std::bit_cast(¶m)); 35 | functor.~Functor(); 36 | } 37 | else { 38 | std::unique_ptr(std::bit_cast(param)); 39 | } 40 | } 41 | static void NTAPI invoker(Parameter param) { 42 | if constexpr (is_small) { 43 | auto &functor = *std::launder(std::bit_cast(¶m)); 44 | functor(); 45 | functor.~Functor(); 46 | } 47 | else { 48 | auto functor_ptr = std::unique_ptr(std::bit_cast(param)); 49 | (*functor_ptr)(); 50 | } 51 | } 52 | }; 53 | 54 | public: 55 | UniqueCallbackAdapter() = default; 56 | template 57 | explicit UniqueCallbackAdapter(Functor &&functor) 58 | : m_param{Helper::param(std::forward(functor))} 59 | , m_deleter{&Helper::deleter} 60 | , m_invoker{&Helper::invoker} {} 61 | UniqueCallbackAdapter(const UniqueCallbackAdapter &) = delete; 62 | UniqueCallbackAdapter &operator=(const UniqueCallbackAdapter &) = delete; 63 | UniqueCallbackAdapter(UniqueCallbackAdapter &&) = delete; 64 | UniqueCallbackAdapter &operator=(UniqueCallbackAdapter &&) = delete; 65 | ~UniqueCallbackAdapter() { m_deleter(m_param); } 66 | 67 | auto c_parameter() const -> Parameter { return m_param; } 68 | auto c_callback() const -> Invoker { return m_invoker; } 69 | 70 | void release() { m_deleter = &UniqueCallbackAdapter::noopParameterFunction; } 71 | }; 72 | -------------------------------------------------------------------------------- /src/meta/flags.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace meta { 5 | 6 | template 7 | struct flags { 8 | static_assert(std::is_enum_v, "flags only works for enums!"); 9 | 10 | using enum_type = T; 11 | using value_type = std::underlying_type_t; 12 | 13 | constexpr flags() noexcept = default; 14 | constexpr flags(const flags &) noexcept = default; 15 | constexpr flags(flags &&) noexcept = default; 16 | 17 | constexpr auto operator=(const flags &) noexcept -> flags & = default; 18 | constexpr auto operator=(flags &&) noexcept -> flags & = default; 19 | 20 | constexpr flags(T e) noexcept 21 | : v(static_cast(e)) {} 22 | 23 | constexpr auto operator=(T e) noexcept -> flags { 24 | v = static_cast(e); 25 | return *this; 26 | } 27 | 28 | constexpr void swap(flags &fl) noexcept { std::swap(v, fl.v); } 29 | 30 | template 31 | constexpr flags(T v, Args... args) noexcept 32 | : v(build(v, args...)) {} 33 | 34 | constexpr bool operator==(flags f) const noexcept { return v == f.v; } 35 | constexpr bool operator!=(flags f) const noexcept { return v != f.v; } 36 | 37 | constexpr auto operator|=(flags f2) noexcept -> flags & { 38 | v |= f2.v; 39 | return *this; 40 | } 41 | constexpr auto operator|=(T e2) noexcept -> flags & { 42 | v |= static_cast(e2); 43 | return *this; 44 | } 45 | 46 | constexpr friend auto operator|(flags f1, flags f2) noexcept -> flags { return {f1.v | f2.v}; } 47 | 48 | constexpr auto clear(flags f = {}) const noexcept -> flags { return {v & ~f.v}; } 49 | constexpr bool has_any(flags f) const noexcept { return (v & f.v) != 0; } 50 | constexpr bool has_all(flags f) const noexcept { return (v & f.v) == f.v; } 51 | 52 | template 53 | constexpr bool clear(T v, Args... args) const noexcept { 54 | return clear(build(v, args...)); 55 | } 56 | template 57 | constexpr bool has_any(T v, Args... args) const noexcept { 58 | return has_any(build(v, args...)); 59 | } 60 | template 61 | constexpr bool has_all(T v, Args... args) const noexcept { 62 | return has_all(build(v, args...)); 63 | } 64 | 65 | private: 66 | constexpr flags(value_type v) noexcept 67 | : v(v) {} 68 | 69 | template 70 | static constexpr auto build(Args... args) noexcept -> flags { 71 | auto val = flags{0}; 72 | const auto x = {((val |= args), 0)...}; 73 | (void)x; 74 | return val; 75 | } 76 | 77 | private: 78 | value_type v; 79 | }; 80 | 81 | } // namespace meta 82 | 83 | #define META_FLAGS_OP(T) \ 84 | constexpr auto operator|(T e1, T e2) noexcept->meta::flags { \ 85 | return meta::flags(e1) | e2; \ 86 | } 87 | -------------------------------------------------------------------------------- /src/BaseRenderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "meta/comptr.h" 3 | 4 | #include 5 | 6 | #include 7 | 8 | // shared between frame updater & window renderer 9 | struct BaseRenderer { 10 | struct InitArgs { 11 | ComPtr device{}; 12 | ComPtr deviceContext{}; 13 | }; 14 | struct Vertex { 15 | float x{}; 16 | float y{}; 17 | float u{}; 18 | float v{}; 19 | }; 20 | using Color = std::array; 21 | 22 | BaseRenderer(InitArgs &&args); 23 | ~BaseRenderer(); 24 | 25 | BaseRenderer() = default; 26 | BaseRenderer(const BaseRenderer &) = default; 27 | BaseRenderer(BaseRenderer &&) = default; 28 | BaseRenderer &operator=(const BaseRenderer &) = default; 29 | BaseRenderer &operator=(BaseRenderer &&) = default; 30 | 31 | auto createShaderTexture(ID3D11Texture2D *texture) const -> ComPtr; 32 | auto createLinearSampler() -> ComPtr; 33 | 34 | auto device() const noexcept -> ID3D11Device * { return m_device.Get(); } 35 | auto deviceContext() const noexcept -> ID3D11DeviceContext * { return m_deviceContext.Get(); } 36 | 37 | void activateNoRenderTarget() const { m_deviceContext->OMSetRenderTargets(0, nullptr, nullptr); } 38 | 39 | void activateDiscreteSampler(int index = 0) const { 40 | m_deviceContext->PSSetSamplers(index, 1, m_discreteSamplerState.GetAddressOf()); 41 | } 42 | 43 | void activateAlphaBlendState() const { 44 | auto const blendFactor = Color{0.f, 0.f, 0.f, 0.f}; 45 | auto const sampleMask = uint32_t{0xffffffffu}; 46 | m_deviceContext->OMSetBlendState(m_alphaBlendState.Get(), blendFactor.data(), sampleMask); 47 | } 48 | void activateNoBlendState() const { 49 | auto const blendFactor = Color{0.f, 0.f, 0.f, 0.f}; 50 | auto const sampleMask = uint32_t{0xffffffffu}; 51 | m_deviceContext->OMSetBlendState(nullptr, blendFactor.data(), sampleMask); 52 | } 53 | 54 | void activateVertexShader() const { 55 | m_deviceContext->VSSetShader(m_vertexShader.Get(), nullptr, 0); 56 | m_deviceContext->IASetInputLayout(m_inputLayout.Get()); 57 | } 58 | void activatePlainPixelShader() const { m_deviceContext->PSSetShader(m_plainPixelShader.Get(), nullptr, 0); } 59 | 60 | void activateTriangleList() const { 61 | m_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); 62 | } 63 | 64 | private: 65 | void createDiscreteSamplerState(); 66 | void createAlphaBlendState(); 67 | void createVertexShader(); 68 | void createPlainPixelShader(); 69 | 70 | private: 71 | ComPtr m_device{}; 72 | ComPtr m_deviceContext{}; 73 | 74 | ComPtr m_discreteSamplerState{}; 75 | ComPtr m_alphaBlendState{}; 76 | 77 | ComPtr m_vertexShader{}; 78 | ComPtr m_inputLayout{}; 79 | ComPtr m_plainPixelShader{}; 80 | }; 81 | -------------------------------------------------------------------------------- /src/win32/ThreadLoop.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "meta/member_method.h" 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace win32 { 10 | 11 | using Milliseconds = std::chrono::milliseconds; 12 | 13 | struct ThreadLoop { 14 | enum Keep { Yes, Remove }; 15 | struct Callback { 16 | template 17 | constexpr static auto make(T *cb) -> Callback { 18 | return { 19 | [](void *ptr, HANDLE handle) -> Keep { 20 | auto p = std::bit_cast(ptr); 21 | return p(handle); 22 | }, 23 | cb}; 24 | } 25 | template 26 | requires(MemberMedthodSig) 27 | constexpr static auto makeMember(MemberMethodClass *obj) -> Callback { 28 | Func *func = [](void *ptr, HANDLE handle) -> Keep { 29 | auto p = std::bit_cast *>(ptr); 30 | return (p->*M)(handle); 31 | }; 32 | return {func, obj}; 33 | } 34 | 35 | auto operator()(HANDLE h) const -> Keep { return m_func(m_ptr, h); } 36 | 37 | private: 38 | using Func = auto(void *, HANDLE) -> Keep; 39 | static auto noopFunc(void *, HANDLE) -> Keep { return Keep::Remove; } 40 | 41 | constexpr Callback(Func *func, void *ptr) 42 | : m_func(func) 43 | , m_ptr(ptr) {} 44 | 45 | Func *m_func = &Callback::noopFunc; 46 | void *m_ptr{}; 47 | }; 48 | 49 | bool isQuit() const { return m_quit; } 50 | bool keepRunning() const { return !m_quit; } 51 | int returnValue() const { return m_returnValue; } 52 | 53 | template 54 | void addAwaitable(HANDLE h, T *cb) { 55 | m_waitHandles.push_back(h); 56 | m_callbacks.push_back(Callback::make(cb)); 57 | } 58 | template 59 | void addAwaitableMember(HANDLE h, T *cb) { 60 | m_waitHandles.push_back(h); 61 | m_callbacks.push_back(Callback::makeMember(cb)); 62 | } 63 | void removeAwaitable(HANDLE h) { 64 | auto it = std::find(m_waitHandles.begin(), m_waitHandles.end(), h); 65 | if (it == m_waitHandles.end()) return; 66 | m_callbacks.erase(m_callbacks.begin() + (it - m_waitHandles.begin())); 67 | m_waitHandles.erase(it); 68 | } 69 | 70 | void enableAllInputs() { m_queueStatus |= QS_ALLINPUT; } 71 | void enableAwaitAlerts() { m_awaitAlerts = true; } 72 | void quit() { m_quit = true; } 73 | 74 | int run() { 75 | while (keepRunning()) { 76 | sleep(); 77 | } 78 | return returnValue(); 79 | } 80 | 81 | void sleep(); 82 | void processCurrentMessages(); 83 | 84 | private: 85 | std::vector m_waitHandles; 86 | std::vector m_callbacks; 87 | Milliseconds m_timeout{INFINITE}; 88 | DWORD m_queueStatus{QS_ALLINPUT}; 89 | bool m_awaitAlerts{}; 90 | bool m_quit{}; 91 | int m_returnValue{}; 92 | }; 93 | 94 | } // namespace win32 95 | -------------------------------------------------------------------------------- /src/CaptureThread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "FrameContext.h" 3 | 4 | #include "meta/comptr.h" 5 | #include "win32/Geometry.h" 6 | #include "win32/Thread.h" 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | struct CapturedUpdate; 15 | 16 | struct Unexpected { 17 | const char *text{}; 18 | }; 19 | 20 | struct Expected { 21 | const char *text{}; 22 | }; 23 | 24 | /// returns the global thread handle (usable in any thread!) 25 | HANDLE GetCurrentThreadHandle(); 26 | 27 | using win32::Point; 28 | using win32::Thread; 29 | 30 | /// Interaction with the Capturing Thread 31 | /// note: all public methods should be called by main thread! 32 | struct CaptureThread { 33 | using SetErrorFunc = void(void *, const std::exception_ptr &); 34 | using SetFrameFunc = void(void *, CapturedUpdate &&, const FrameContext &, size_t threadIndex); 35 | 36 | struct Config { 37 | size_t threadIndex{}; // identifier to this thread 38 | 39 | SetErrorFunc *setErrorCallback{&CaptureThread::noopSetErrorCallback}; 40 | SetFrameFunc *setFrameCallback{&CaptureThread::noopSetFrameCallback}; 41 | void *callbackPtr{}; 42 | 43 | /// note: callbacks are invoked inside the CaptureThread! 44 | template 45 | void setCallbacks(T *p) { 46 | callbackPtr = p; 47 | setErrorCallback = [](void *ptr, const std::exception_ptr &exception) { 48 | auto *cb = std::bit_cast(ptr); 49 | cb->setError(exception); 50 | }; 51 | setFrameCallback = [](void *ptr, CapturedUpdate &&update, const FrameContext &context, size_t threadIndex) { 52 | auto *cb = std::bit_cast(ptr); 53 | cb->setFrame(std::move(update), context, threadIndex); 54 | }; 55 | } 56 | }; 57 | 58 | CaptureThread(const Config &config) noexcept 59 | : m_config(config) {} 60 | 61 | ~CaptureThread(); 62 | 63 | struct StartArgs { 64 | int display{}; // index of the display to capture 65 | ComPtr device; // device used for capturing 66 | Point offset{}; // offset from desktop to target coordinates 67 | }; 68 | void start(StartArgs &&args); ///< start a stopped thread 69 | 70 | void next(); ///< thread starts to capture the next frame 71 | void stop(); ///< signal thread to stop and waits 72 | 73 | private: 74 | void capture_next(); 75 | void capture_stop(); 76 | 77 | void run(); 78 | 79 | void initCapture(); 80 | void handleDeviceError(const char *text, HRESULT, std::initializer_list expected); 81 | 82 | auto captureUpdate() -> std::optional; 83 | 84 | static void noopSetErrorCallback(void *, const std::exception_ptr &) {} 85 | static void noopSetFrameCallback(void *, CapturedUpdate &&, const FrameContext &, size_t /*threadIndex*/) {} 86 | 87 | private: 88 | Config m_config; 89 | int m_display{}; 90 | 91 | FrameContext m_context{}; 92 | ComPtr m_device{}; 93 | Thread m_thread{}; 94 | bool m_keepRunning = true; 95 | 96 | bool m_doCapture = true; 97 | 98 | ComPtr m_dupl; 99 | std::optional m_stdThread; 100 | }; 101 | -------------------------------------------------------------------------------- /src/DuplicationController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CaptureThread.h" 3 | #include "FrameUpdater.h" 4 | #include "MainController.h" 5 | #include "Model.h" 6 | #include "PointerUpdater.h" 7 | #include "RenderThread.h" 8 | 9 | #include "win32/Thread.h" 10 | #include "win32/ThreadLoop.h" 11 | #include "win32/WaitableTimer.h" 12 | #include "win32/Window.h" 13 | 14 | #include 15 | 16 | namespace deskdup { 17 | 18 | using win32::Handle; 19 | using win32::Thread; 20 | using win32::ThreadLoop; 21 | using win32::WaitableTimer; 22 | using win32::Window; 23 | using win32::WindowClass; 24 | using win32::WindowWithMessages; 25 | 26 | /// main controller that manages the duplication party 27 | /// notes: 28 | /// * all public methods are called from the MainThread 29 | /// * capturing happens in the CaptureThread 30 | /// * rendering happens in the RenderThread 31 | struct DuplicationController { 32 | struct Args { 33 | WindowClass const &windowClass; 34 | MainController &mainController; 35 | Thread &mainThread; 36 | WindowWithMessages &outputWindow; 37 | }; 38 | DuplicationController(Args const &); 39 | ~DuplicationController(); 40 | 41 | void updateDuplicationStatus(DuplicationStatus); 42 | void updateOutputDimension(Dimension); 43 | void updateOutputZoom(float zoom); 44 | void updateCaptureOffset(Vec2f); 45 | void restart(); 46 | 47 | private: 48 | enum class Status { 49 | Stopped, // from Stopping 50 | Initial = Stopped, 51 | Starting, // from Initial/Stopped 52 | Live, // from Starting or Resuming 53 | Stopping, // from Live 54 | Paused, // from Live 55 | Resuming, // from Paused 56 | RetryStarting, // from Starting 57 | Failed, // given up 58 | }; 59 | 60 | private: 61 | auto renderThreadConfig(OperationModeLens) -> RenderThread::Config; 62 | auto captureThreadConfig() -> CaptureThread::Config; 63 | 64 | void startOnMain(); 65 | void pauseOnMain(); 66 | void stopOnMain(); 67 | void resetOnMain(); 68 | void updateStatusOnMain(Status); 69 | 70 | void initCaptureThread(); 71 | void updateCaptureStatus(); 72 | void startCaptureThread(HANDLE targetHandle); 73 | void awaitRetry(); 74 | void retryTimeout(); 75 | 76 | private: 77 | auto forwardRenderMessage(const win32::WindowMessage &) -> win32::OptLRESULT; 78 | 79 | private: 80 | friend struct ::WindowRenderer; 81 | friend struct ::CaptureThread; 82 | void setError(const std::exception_ptr &); // called on CaptureThread or RenderThread 83 | void setFrame(CapturedUpdate &&, const FrameContext &, size_t threadIndex); // called on RenderThread 84 | 85 | private: 86 | void setFrameOnRender(CapturedUpdate &&, const FrameContext &, size_t threadIndex); 87 | 88 | private: 89 | std::atomic m_status{}; 90 | Rect m_displayRect{}; 91 | 92 | WindowClass const &m_windowClass; 93 | MainController &m_controller; 94 | Thread &m_mainThread; 95 | WindowWithMessages &m_outputWindow; 96 | WindowWithMessages m_renderWindow; 97 | 98 | RenderThread m_renderThread; 99 | CaptureThread m_captureThread; 100 | 101 | ComPtr m_targetTexture; 102 | std::optional m_frameUpdater; 103 | PointerUpdater m_pointerUpdater; 104 | 105 | WaitableTimer m_retryTimer; 106 | }; 107 | 108 | } // namespace deskdup 109 | -------------------------------------------------------------------------------- /src/TaskbarButtons.cpp: -------------------------------------------------------------------------------- 1 | #include "TaskbarButtons.h" 2 | 3 | namespace deskdup { 4 | 5 | enum ThumbButton { Maximized, Freezed, VisibleArea }; 6 | 7 | TaskbarButtons::TaskbarButtons() { 8 | m_taskbarCreatedMessage = RegisterWindowMessageW(L"TaskbarButtonCreated"); 9 | 10 | ChangeWindowMessageFilter(m_taskbarCreatedMessage, MSGFLT_ADD); 11 | ChangeWindowMessageFilter(WM_COMMAND, MSGFLT_ADD); 12 | } 13 | 14 | void TaskbarButtons::createButtons(Window &window, State const &state) { 15 | m_hasButtons = true; 16 | m_taskbarList = win32::TaskbarList{window.handle()}; 17 | m_taskbarList.setButtonFlags(ThumbButton::Maximized, win32::ThumbButtonFlag::Enabled); 18 | m_taskbarList.setButtonTooltip(ThumbButton::Maximized, L"toggle fullscreen"); 19 | visualizeMaximized(state.outputMaximized); 20 | 21 | m_taskbarList.setButtonFlags(ThumbButton::Freezed, win32::ThumbButtonFlag::Enabled); 22 | m_taskbarList.setButtonTooltip(ThumbButton::Freezed, L"toggle freezed"); 23 | visualizeDuplicationStatus(state.duplicationStatus, state.config.pauseRendering); 24 | 25 | m_taskbarList.setButtonFlags(ThumbButton::VisibleArea, win32::ThumbButtonFlag::Enabled); 26 | m_taskbarList.setButtonTooltip(ThumbButton::VisibleArea, L"toggle area"); 27 | visualizeVisibleArea(state.config.isCaptureAreaShown); 28 | 29 | finalizeButtons(); 30 | } 31 | 32 | bool TaskbarButtons::handleCommand(int command, MainController &controller) { 33 | switch (command) { 34 | case ThumbButton::Maximized: return controller.toggleOutputMaximize(), true; 35 | case ThumbButton::Freezed: return controller.togglePause(), true; 36 | case ThumbButton::VisibleArea: return controller.toggleVisualizeCaptureArea(), true; 37 | } 38 | return false; 39 | } 40 | 41 | void TaskbarButtons::updateMaximized(bool isMaximized) { 42 | if (!m_hasButtons) return; 43 | visualizeMaximized(isMaximized); 44 | finalizeButtons(); 45 | } 46 | 47 | void TaskbarButtons::updateSystemStatus(SystemStatus status) { 48 | if (!m_hasButtons) return; 49 | visualizeSystemStatus(status); 50 | } 51 | 52 | void TaskbarButtons::updateDuplicationStatus(DuplicationStatus status, PauseRendering pauseRendering) { 53 | if (!m_hasButtons) return; 54 | visualizeDuplicationStatus(status, pauseRendering); 55 | finalizeButtons(); 56 | } 57 | 58 | void TaskbarButtons::updateVisibleArea(bool isShown) { 59 | if (!m_hasButtons) return; 60 | visualizeVisibleArea(isShown); 61 | finalizeButtons(); 62 | } 63 | 64 | void TaskbarButtons::visualizeMaximized(bool isMaximized) { 65 | auto icon = isMaximized ? wchar_t{0xE923} : wchar_t{0xE922}; 66 | m_taskbarList.setButtonLetterIcon(ThumbButton::Maximized, icon); 67 | m_updatedButtons = true; 68 | } 69 | 70 | void TaskbarButtons::visualizeSystemStatus(SystemStatus status) { 71 | switch (status) { 72 | case SystemStatus::Neutral: m_taskbarList.setProgressFlags(win32::ProgressFlag::Disabled); break; 73 | case SystemStatus::Green: 74 | m_taskbarList.setProgressFlags(win32::ProgressFlag::Normal); 75 | m_taskbarList.setProgressValue(1, 1); 76 | break; 77 | case SystemStatus::Yellow: 78 | m_taskbarList.setProgressFlags(win32::ProgressFlag::Paused); 79 | m_taskbarList.setProgressValue(1, 1); 80 | break; 81 | case SystemStatus::Red: 82 | m_taskbarList.setProgressFlags(win32::ProgressFlag::Error); 83 | m_taskbarList.setProgressValue(1, 3); 84 | break; 85 | } 86 | } 87 | 88 | void TaskbarButtons::visualizeDuplicationStatus(DuplicationStatus status, PauseRendering pauseRendering) { 89 | if (status == DuplicationStatus::Live) { 90 | m_taskbarList.setButtonLetterIcon(1, 0xE103, RGB(255, 200, 10)); 91 | } 92 | else { 93 | using enum PauseRendering; 94 | switch (pauseRendering) { 95 | case LastFrame: m_taskbarList.setButtonLetterIcon(1, 0xE768, RGB(100, 255, 100)); break; 96 | case Black: m_taskbarList.setButtonLetterIcon(1, 0xE747, RGB(100, 100, 100)); break; 97 | case White: m_taskbarList.setButtonLetterIcon(1, 0xE747, RGB(255, 255, 255)); break; 98 | } 99 | } 100 | m_updatedButtons = true; 101 | } 102 | 103 | constexpr static auto colorKey = RGB(0xFF, 0x20, 0xFF); 104 | 105 | void TaskbarButtons::visualizeVisibleArea(bool isShown) { 106 | auto color = isShown ? RGB(128, 128, 128) : colorKey; 107 | m_taskbarList.setButtonLetterIcon(ThumbButton::VisibleArea, 0xEF20, color); 108 | m_updatedButtons = true; 109 | } 110 | 111 | void TaskbarButtons::finalizeButtons() { 112 | m_updatedButtons = false; 113 | m_taskbarList.updateThumbButtons(); 114 | } 115 | 116 | } // namespace deskdup 117 | -------------------------------------------------------------------------------- /src/Model.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "win32/DisplayMonitor.h" 3 | #include "win32/Geometry.h" 4 | 5 | #include 6 | 7 | namespace deskdup { 8 | 9 | using win32::Dimension; 10 | using win32::MonitorInfo; 11 | using win32::Point; 12 | using win32::Rect; 13 | using win32::Vec2f; 14 | 15 | enum class SystemStatus { 16 | Neutral, 17 | Green, 18 | Yellow, 19 | Red, 20 | }; 21 | enum class OperationMode { 22 | PresentMirror, ///< capture one area of screen and present the mirror somewhere else 23 | CaptureArea, ///< capture the same area and replay it for capture a window to pick it up 24 | }; 25 | 26 | enum class CaptureVisualization { 27 | Border, 28 | // Stripes, 29 | }; 30 | enum class DuplicationStatus { 31 | Live, 32 | Pause, 33 | }; 34 | enum class PauseRendering { 35 | LastFrame, 36 | Black, 37 | White, 38 | }; 39 | using MonitorIndex = int; 40 | 41 | /// Data that should be stored from one session to the next 42 | /// note: some sanitization is needed to avoid unusable state (like monitor does not exist) 43 | struct Config final { 44 | OperationMode operationMode{}; 45 | PauseRendering pauseRendering{}; 46 | 47 | MonitorIndex captureMonitor{}; // 0 = main monitor 48 | Vec2f captureOffset{}; ///< relative to the capture monitor 49 | bool isCaptureAreaShown{}; 50 | CaptureVisualization captureVisualization{}; 51 | 52 | Point outputTopLeft{}; 53 | Dimension outputDimension{}; 54 | float outputZoom{1.0f}; 55 | 56 | auto outputRect() const -> Rect { return Rect{outputTopLeft, outputDimension}; } 57 | }; 58 | 59 | struct Monitor final { 60 | HMONITOR handle{}; 61 | MonitorInfo info{}; 62 | 63 | bool operator==(Monitor const &) const = default; 64 | }; 65 | using Monitors = std::vector; 66 | 67 | /// Data that is produced locally and is valid only at the current state 68 | struct State final { 69 | Config config{}; 70 | bool outputMaximized{}; 71 | SystemStatus systemStatus{}; 72 | DuplicationStatus duplicationStatus{}; 73 | Monitors monitors{}; ///< all currently available monitors 74 | MonitorIndex outputMonitor{}; ///< 75 | Rect captureMonitorRect{}; ///< 76 | 77 | auto computeIndexForMonitorHandle(HMONITOR) -> MonitorIndex; 78 | }; 79 | 80 | struct PresentMirrorLens final { 81 | static auto captureMonitor(State const &m) -> MonitorIndex { return m.config.captureMonitor; } 82 | static auto captureOffset(State const &m) -> Vec2f { return m.config.captureOffset; } 83 | static auto outputZoom(State const &m) -> float { return m.config.outputZoom; } 84 | 85 | // note: depends on captureMonitorRect, captureOffset, outputDimension, outputZoom 86 | static auto captureAreaRect(State const &m) -> Rect; 87 | }; 88 | 89 | struct CaptureAreaLens final { 90 | static auto captureMonitor(State const &m) -> MonitorIndex { return m.outputMonitor; } 91 | static auto captureOffset(State const &m) -> Vec2f; 92 | static auto outputZoom(State const &) -> float { return 1.0f; } 93 | static auto captureAreaRect(State const &m) -> Rect { return m.config.outputRect(); } 94 | }; 95 | 96 | /// Combination of the application data 97 | /// Adds accessors that give different answers depending on the state of the program 98 | struct OperationModeLens final { 99 | explicit OperationModeLens(State const &state) 100 | : m{state} {} 101 | 102 | auto state() const -> State const & { return m; } 103 | auto config() const -> Config const & { return m.config; } 104 | 105 | auto captureMonitor() const -> MonitorIndex { 106 | switch (m.config.operationMode) { 107 | case OperationMode::PresentMirror: return PresentMirrorLens::captureMonitor(m); 108 | case OperationMode::CaptureArea: return CaptureAreaLens::captureMonitor(m); 109 | } 110 | return {}; 111 | } 112 | auto captureOffset() const -> Vec2f { 113 | switch (m.config.operationMode) { 114 | case OperationMode::PresentMirror: return PresentMirrorLens::captureOffset(m); 115 | case OperationMode::CaptureArea: return CaptureAreaLens::captureOffset(m); 116 | } 117 | return {}; 118 | } 119 | auto outputZoom() const -> float { 120 | switch (m.config.operationMode) { 121 | case OperationMode::PresentMirror: return PresentMirrorLens::outputZoom(m); 122 | case OperationMode::CaptureArea: return CaptureAreaLens::outputZoom(m); 123 | } 124 | return {}; 125 | } 126 | auto captureAreaRect() const -> Rect { 127 | switch (m.config.operationMode) { 128 | case OperationMode::PresentMirror: return PresentMirrorLens::captureAreaRect(m); 129 | case OperationMode::CaptureArea: return CaptureAreaLens::captureAreaRect(m); 130 | } 131 | return {}; 132 | } 133 | 134 | private: 135 | State const &m; 136 | }; 137 | 138 | } // namespace deskdup 139 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | # Windows Desktop Duplication Tool 2 | 3 | This is a nifty tool to host live coding in presentations or online screen grabbers. 4 | 5 | ## Two in One 6 | 7 | Since 3.0 we have two modes of operations. 8 | 9 | ### Presenter Mode 10 | 11 | You have your IDE and private notes on your main screen. 12 | The tool only captures the code portion of this screen and shows it fullscreen on your presentation monitor or projector. 13 | 14 | image::/docs/usage-schema.png?raw=true[Usage Schema,583,168] 15 | 16 | Personally I use this mode to host C\++ usergroup meetings, team meetings and professional C++ trainings. 17 | 18 | ### Capture Area Mode 19 | 20 | You select a portion of the screen by moving the Window around. 21 | Once you go "Live" (Context Menu or Taskbar Menu) the Window will move into the background and display whatever is displayed on top of it. 22 | 23 | Hint: Your mouse cursors will trail if you have no window in front of it. 24 | 25 | Personally I use this mode to share part of my screen in Jitsi, Matrix, Big Blue Button, Teams, Zoom or any other conferencing tool. 26 | 27 | Note: As soon as you select the window again, the capture will be pause and the contents of the window will be transparent to allow repositioning of the window without flickering. 28 | 29 | ## Features 30 | 31 | By top left part of the primary screen is duplicated to a window on the right part of the same screen. 32 | 33 | Interactions: 34 | 35 | * Move the window whereever you need it. Beamers for live coding and presentations. 36 | * Right click with your mouse to open a context menu 37 | ** Pause / Start the screen grabbing operations 38 | ** Select some common resolutions for your screen share window 39 | ** Select the monitor to capture for Presenter Mode (Capture Area mode will select the monitor the window is in) 40 | ** Switch between Presenter and Capture Area Mode 41 | * Double Left Mouseclick maximizes the window. 42 | ** The entire screen is now mirroring (no window frame) 43 | ** We prevent Windows from going to sleep mode in this presentation mode 44 | * The Zoom also changes captured area. 45 | ** pass:[Ctrl] + pass:[Mousewheel ↕/+/-] allows you to zoom 46 | ** pass:[CtrlShift] + pass:[Mousewheel ↕] zooms in smaller steps 47 | ** pass:[Ctrl + 0] reset zoom to 1:1 48 | * Moving the captured area. 49 | ** pass:[Shift] + pass:[Left Mouse Button] + pass:[Dragging ⇔⇕] allows you to move the visible portion 50 | ** pass:[Shift + ///] aligns the mirrored image to the border 51 | * Move Window into captured area. 52 | ** pass:[2×Right Mouse Button] + [Focus other Window] will position and resize the other window into the mirrored area. Not all windows will like this. 53 | * Interactions: 54 | ** Note: You can hover over the window icon in the taskbar and use the buttons there. 55 | ** pass:[Shift + Right Mouse Button] toggle a visualisation of the captured area 56 | ** pass:[Ctrl + P] toggle pause. Freezes the image. 57 | 58 | The tool is optimized to be very response and save CPU time. 59 | 60 | ## History 61 | 62 | ### 3.x - TBA 63 | 64 | * Your Ideas are welcome (see Contributions) 65 | 66 | ### 3.0-beta - 2023-12-26 67 | 68 | * new capture area mode 69 | * keep capture alive while moving or resizing the window 70 | * select the monitor where the capture is taken 71 | * capture area indicator is now fully transparent for clicks 72 | * added context menu 73 | * also show live and frozen state in capture area border colors 74 | 75 | ### 2.0 - 2021-01-17 76 | 77 | * pause / freeze feature 78 | * visualization of state in taskbar 79 | * taskbar buttons for interactions 80 | * mitigated the double buffering bug (last change was missing) 81 | * performance enhancements (less CPU und GPU required) 82 | * cleaned up code base (easier to start new features) 83 | 84 | ### 1.0 - 2017-04-16 (Revision Easter Release) 85 | 86 | * first public version 87 | * hand crafted at the Revision Demo Party 88 | * basic features are working 89 | 90 | ## Requirements 91 | 92 | * DirectX 11 93 | * Windows 10 94 | 95 | 96 | ## Build yourself 97 | 98 | The desktop duplicator was build using QtCreator with Qbs and MSVC2019. 99 | 100 | The only other thing you need is the DirectX and Windows and WRL headers. All included in the Windows 10 SDK. 101 | 102 | If you have issues please ask. 103 | 104 | 105 | ## License 106 | 107 | Read the LICENSE file! 108 | 109 | 110 | ## Contributions 111 | 112 | If you have issues or questions feel free to open a ticket. 113 | 114 | If you can answer a question in a ticket, help is appreciated. 115 | 116 | If you like the tool, leave a star on Github and spread the love. 117 | 118 | All usefull pull requests are welcome! If you have an idea and are unsure, please open a ticket for discussions. 119 | 120 | Enjoy! 121 | 122 | ## \0 123 | -------------------------------------------------------------------------------- /src/WindowRenderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "BaseRenderer.h" 3 | #include "win32/Geometry.h" 4 | #include "win32/Handle.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | struct PointerBuffer; 11 | 12 | using win32::Dimension; 13 | using win32::Handle; 14 | using win32::Point; 15 | using win32::Vec2f; 16 | 17 | // manages state how to render background & pointer to output window 18 | struct WindowRenderer { 19 | using SetErrorFunc = void(void *, const std::exception_ptr &); 20 | struct Args { 21 | PointerBuffer const &pointerBuffer; 22 | float outputZoom{1.0f}; 23 | Vec2f captureOffset{}; 24 | 25 | SetErrorFunc *setErrorCallback{&WindowRenderer::noopSetErrorCallback}; 26 | void *callbackPtr{}; 27 | 28 | /// note: callbacks are invoked inside the RenderThread! 29 | template 30 | void setCallbacks(T *p) { 31 | callbackPtr = p; 32 | setErrorCallback = [](void *ptr, const std::exception_ptr &exception) { 33 | auto *cb = std::bit_cast(ptr); 34 | cb->setError(exception); 35 | }; 36 | } 37 | }; 38 | struct InitArgs { 39 | BaseRenderer::InitArgs basic{}; 40 | HWND windowHandle{}; // window to render to 41 | Dimension windowDimension{}; 42 | ComPtr texture{}; // texture is rendered as quad 43 | }; 44 | using Vertex = BaseRenderer::Vertex; 45 | 46 | WindowRenderer(Args const &); 47 | 48 | auto isInitialized() const -> bool { return m_dx.has_value(); } 49 | 50 | void init(InitArgs &&args); 51 | void reset() noexcept; 52 | 53 | auto frameLatencyWaitable() -> Handle; 54 | 55 | bool resize(Dimension size) noexcept; 56 | void zoomOutput(float zoom) noexcept; 57 | void updateOffset(Vec2f offset) noexcept; 58 | void updateHideFrame(bool) noexcept; 59 | 60 | void render(); 61 | 62 | private: 63 | void renderBlack(); 64 | void renderFrame(); 65 | void renderPointer(); 66 | void swap(); 67 | 68 | void resizeSwapBuffer(); 69 | void setViewPort(); 70 | void updatePointerShape(const PointerBuffer &pointer); 71 | void updatePointerVertices(const PointerBuffer &pointer); 72 | 73 | static void noopSetErrorCallback(void *, const std::exception_ptr &) {} 74 | 75 | private: 76 | Args m_args; 77 | Dimension m_size{}; 78 | 79 | bool m_pendingResizeBuffers = false; 80 | 81 | uint64_t m_lastPointerShapeUpdate = 0; 82 | uint64_t m_lastPointerPositionUpdate = 0; 83 | 84 | struct Resources : BaseRenderer { 85 | Resources(WindowRenderer::InitArgs &&args); 86 | 87 | private: 88 | void createBackgroundTextureShaderResource(); 89 | void createBackgroundVertexBuffer(); 90 | void createSwapChain(HWND windowHandle); 91 | void createMaskedPixelShader(); 92 | void createLinearSamplerState(); 93 | void createPointerVertexBuffer(); 94 | 95 | public: 96 | void createRenderTarget(); 97 | 98 | void clearRenderTarget(Color c) { deviceContext()->ClearRenderTargetView(renderTarget.Get(), c.data()); } 99 | void activateRenderTarget() { deviceContext()->OMSetRenderTargets(1, renderTarget.GetAddressOf(), nullptr); } 100 | 101 | void activateBackgroundTexture() { 102 | deviceContext()->PSSetShaderResources(0, 1, backgroundTextureShaderResource.GetAddressOf()); 103 | } 104 | void activateBackgroundVertexBuffer() { 105 | auto const stride = uint32_t{sizeof(Vertex)}; 106 | auto const offset = uint32_t{0}; 107 | deviceContext()->IASetVertexBuffers(0, 1, backgroundVertexBuffer.GetAddressOf(), &stride, &offset); 108 | } 109 | 110 | void activateMaskedPixelShader() const { deviceContext()->PSSetShader(maskedPixelShader.Get(), nullptr, 0); } 111 | void activatePointerTexture(int index = 0) { 112 | deviceContext()->PSSetShaderResources(index, 1, pointerTextureShaderResource.GetAddressOf()); 113 | } 114 | void activateLinearSampler(int index = 0) const { 115 | deviceContext()->PSSetSamplers(index, 1, linearSamplerState.GetAddressOf()); 116 | } 117 | void activatePointerVertexBuffer() { 118 | auto const stride = uint32_t{sizeof(Vertex)}; 119 | auto const offset = uint32_t{0}; 120 | deviceContext()->IASetVertexBuffers(0, 1, pointerVertexBuffer.GetAddressOf(), &stride, &offset); 121 | } 122 | 123 | ComPtr backgroundTexture{}; 124 | ComPtr backgroundTextureShaderResource{}; 125 | ComPtr backgroundVertexBuffer{}; 126 | ComPtr swapChain{}; 127 | ComPtr renderTarget{}; 128 | ComPtr maskedPixelShader{}; 129 | ComPtr linearSamplerState{}; 130 | 131 | ComPtr pointerTexture{}; 132 | ComPtr pointerTextureShaderResource{}; 133 | ComPtr pointerVertexBuffer{}; 134 | }; 135 | 136 | std::optional m_dx{}; 137 | }; 138 | -------------------------------------------------------------------------------- /src/BaseRenderer.cpp: -------------------------------------------------------------------------------- 1 | #include "BaseRenderer.h" 2 | 3 | #include "renderer.h" 4 | 5 | #include "PlainPixelShader.h" 6 | #include "VertexShader.h" 7 | 8 | using Error = renderer::Error; 9 | 10 | BaseRenderer::BaseRenderer(InitArgs &&args) 11 | : m_device(std::move(args.device)) 12 | , m_deviceContext(std::move(args.deviceContext)) { 13 | createDiscreteSamplerState(); 14 | createAlphaBlendState(); 15 | createVertexShader(); 16 | createPlainPixelShader(); 17 | } 18 | 19 | BaseRenderer::~BaseRenderer() { 20 | // ensure device is clean, so we can create a new one! 21 | if (m_deviceContext) { 22 | m_deviceContext->ClearState(); 23 | m_deviceContext->Flush(); 24 | } 25 | } 26 | 27 | auto BaseRenderer::createShaderTexture(ID3D11Texture2D *texture) const -> ComPtr { 28 | 29 | auto description = D3D11_TEXTURE2D_DESC{}; 30 | texture->GetDesc(&description); 31 | 32 | auto const shader_resource_description = D3D11_SHADER_RESOURCE_VIEW_DESC{ 33 | .Format = description.Format, 34 | .ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D, 35 | .Texture2D = 36 | D3D11_TEX2D_SRV{ 37 | .MostDetailedMip = description.MipLevels - 1, 38 | .MipLevels = description.MipLevels, 39 | }, 40 | }; 41 | auto shader_resource = ComPtr{}; 42 | auto const result = m_device->CreateShaderResourceView(texture, &shader_resource_description, &shader_resource); 43 | if (IS_ERROR(result)) throw Error{result, "Failed to create shader texture resource"}; 44 | return shader_resource; 45 | } 46 | 47 | auto BaseRenderer::createLinearSampler() -> ComPtr { 48 | auto const description = D3D11_SAMPLER_DESC{ 49 | .Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR, 50 | .AddressU = D3D11_TEXTURE_ADDRESS_CLAMP, 51 | .AddressV = D3D11_TEXTURE_ADDRESS_CLAMP, 52 | .AddressW = D3D11_TEXTURE_ADDRESS_CLAMP, 53 | .MipLODBias = {}, 54 | .MaxAnisotropy = {}, 55 | .ComparisonFunc = D3D11_COMPARISON_NEVER, 56 | .BorderColor = {}, 57 | .MinLOD = 0, 58 | .MaxLOD = D3D11_FLOAT32_MAX, 59 | }; 60 | ComPtr sampler; 61 | auto const result = m_device->CreateSamplerState(&description, &sampler); 62 | if (IS_ERROR(result)) throw Error{result, "Failed to create linear sampler state"}; 63 | return sampler; 64 | } 65 | 66 | void BaseRenderer::createDiscreteSamplerState() { 67 | auto const description = D3D11_SAMPLER_DESC{ 68 | .Filter = D3D11_FILTER_MIN_MAG_MIP_POINT, 69 | .AddressU = D3D11_TEXTURE_ADDRESS_CLAMP, 70 | .AddressV = D3D11_TEXTURE_ADDRESS_CLAMP, 71 | .AddressW = D3D11_TEXTURE_ADDRESS_CLAMP, 72 | .MipLODBias = {}, 73 | .MaxAnisotropy = {}, 74 | .ComparisonFunc = D3D11_COMPARISON_NEVER, 75 | .BorderColor = {}, 76 | .MinLOD = 0, 77 | .MaxLOD = D3D11_FLOAT32_MAX, 78 | }; 79 | auto const result = m_device->CreateSamplerState(&description, &m_discreteSamplerState); 80 | if (IS_ERROR(result)) throw Error{result, "Failed to create discrete sampler state"}; 81 | } 82 | 83 | void BaseRenderer::createAlphaBlendState() { 84 | auto description = D3D11_BLEND_DESC{ 85 | .AlphaToCoverageEnable = FALSE, 86 | .IndependentBlendEnable = FALSE, 87 | .RenderTarget = 88 | { 89 | D3D11_RENDER_TARGET_BLEND_DESC{ 90 | .BlendEnable = TRUE, 91 | .SrcBlend = D3D11_BLEND_SRC_ALPHA, 92 | .DestBlend = D3D11_BLEND_INV_SRC_ALPHA, 93 | .BlendOp = D3D11_BLEND_OP_ADD, 94 | .SrcBlendAlpha = D3D11_BLEND_ONE, 95 | .DestBlendAlpha = D3D11_BLEND_ZERO, 96 | .BlendOpAlpha = D3D11_BLEND_OP_ADD, 97 | .RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL, 98 | }, 99 | }, 100 | }; 101 | auto const result = m_device->CreateBlendState(&description, &m_alphaBlendState); 102 | if (IS_ERROR(result)) throw Error{result, "Failed to create blend state"}; 103 | } 104 | 105 | void BaseRenderer::createVertexShader() { 106 | auto const size = ARRAYSIZE(g_VertexShader); 107 | auto shader = &g_VertexShader[0]; 108 | static constexpr ID3D11ClassLinkage *noLinkage = nullptr; 109 | auto result = m_device->CreateVertexShader(shader, size, noLinkage, &m_vertexShader); 110 | if (IS_ERROR(result)) throw Error{result, "Failed to create vertex shader"}; 111 | 112 | static auto const layout = std::array{ 113 | D3D11_INPUT_ELEMENT_DESC{"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}, 114 | D3D11_INPUT_ELEMENT_DESC{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 8, D3D11_INPUT_PER_VERTEX_DATA, 0}}; 115 | auto const layout_size = static_cast(layout.size()); 116 | result = m_device->CreateInputLayout(layout.data(), layout_size, shader, size, &m_inputLayout); 117 | if (IS_ERROR(result)) throw Error{result, "Failed to create input layout"}; 118 | } 119 | 120 | void BaseRenderer::createPlainPixelShader() { 121 | auto const size = ARRAYSIZE(g_PlainPixelShader); 122 | auto shader = &g_PlainPixelShader[0]; 123 | static constexpr ID3D11ClassLinkage *noLinkage = nullptr; 124 | auto const result = m_device->CreatePixelShader(shader, size, noLinkage, &m_plainPixelShader); 125 | if (IS_ERROR(result)) throw Error{result, "Failed to create pixel shader"}; 126 | } 127 | -------------------------------------------------------------------------------- /DesktopDuplicator.qbs: -------------------------------------------------------------------------------- 1 | import qbs 2 | 3 | Project { 4 | qbsSearchPaths: [ "qbs/" ] 5 | 6 | CppApplication { 7 | name: "Desktop Duplicator" 8 | targetName: "deskdupl" 9 | consoleApplication: false 10 | 11 | Depends { name: 'cpp' } 12 | cpp.cxxLanguageVersion: "c++23" 13 | cpp.treatWarningsAsErrors: true 14 | cpp.enableRtti: false // disable runtime type information for faster build and smaller object files and executable 15 | 16 | cpp.minimumWindowsVersion: "10.0" 17 | cpp.generateManifestFile: false 18 | cpp.includePaths: 'src' 19 | cpp.cxxFlags: ['/analyze', '/Zc:char8_t-'] 20 | cpp.defines: ['NOMINMAX'] 21 | cpp.dynamicLibraries: ['d3d11', "User32", "Gdi32", "Shell32", "Ole32", "Comctl32"] 22 | 23 | Properties { 24 | condition: qbs.toolchain.contains('msvc') 25 | cpp.cxxFlags: outer.concat( 26 | "/permissive-", "/Zc:__cplusplus", // best C++ compatibilty 27 | "/Zc:inline", // do not include inline code in object files 28 | "/Zc:throwingNew", // avoid redundant null checks after new 29 | "/diagnostics:caret", // better error postions 30 | "/W4", // enable all warnings 31 | "/experimental:external", "/external:anglebrackets", "/external:W0" // ignore warnings from external headers 32 | ) 33 | } 34 | 35 | Depends { name: 'hlsl' } 36 | hlsl.shaderModel: '4_0_level_9_3' 37 | 38 | Group { 39 | name: 'PCH' 40 | prefix: 'src/' 41 | files: ['stable.h'] 42 | fileTags: ["cpp_pch_src"] 43 | } 44 | Group { 45 | name: 'Meta' 46 | prefix: 'src/meta/' 47 | files: [ 48 | "callback_adapter.h", 49 | "comptr.h", 50 | "flags.h", 51 | "fromByteSpan.h", 52 | "member_method.h", 53 | "scope_guard.h", 54 | ] 55 | } 56 | Group { 57 | name: 'Win32' 58 | prefix: 'src/win32/' 59 | files: [ 60 | "DisplayMonitor.cpp", 61 | "DisplayMonitor.h", 62 | "Dpi.h", 63 | "Geometry.h", 64 | "Geometry.ostream.h", 65 | "Handle.h", 66 | "PowerRequest.cpp", 67 | "PowerRequest.h", 68 | "Process.cpp", 69 | "Process.h", 70 | "TaskbarList.cpp", 71 | "TaskbarList.h", 72 | "Thread.cpp", 73 | "Thread.h", 74 | "ThreadLoop.cpp", 75 | "ThreadLoop.h", 76 | "WaitableTimer.cpp", 77 | "WaitableTimer.h", 78 | "Window.cpp", 79 | "Window.h", 80 | "Window.ostream.h", 81 | "WindowMessageHandler.h", 82 | ] 83 | } 84 | Group { 85 | name: 'Main' 86 | prefix: "src/" 87 | 88 | Group { 89 | name: 'Pixelshaders' 90 | hlsl.shaderType: 'ps' 91 | files: ["MaskedPixelShader.hlsl", "PlainPixelShader.hlsl"] 92 | } 93 | Group { 94 | name: 'Vertexshaders' 95 | hlsl.shaderType: 'vs' 96 | files: ["VertexShader.hlsl"] 97 | } 98 | Group { 99 | name: 'Capture' 100 | files: [ 101 | "CaptureThread.cpp", 102 | "CaptureThread.h", 103 | "CapturedUpdate.h", 104 | "FrameContext.h", 105 | ] 106 | } 107 | Group { 108 | name: 'Output' 109 | files: [ 110 | "BaseRenderer.cpp", 111 | "BaseRenderer.h", 112 | "FrameUpdater.cpp", 113 | "FrameUpdater.h", 114 | "PointerUpdater.cpp", 115 | "PointerUpdater.h", 116 | "WindowRenderer.cpp", 117 | "WindowRenderer.h", 118 | "renderer.cpp", 119 | "renderer.h", ] 120 | } 121 | Group { 122 | name: 'Application' 123 | files: [ 124 | "CaptureAreaWindow.cpp", 125 | "CaptureAreaWindow.h", 126 | "DuplicationController.cpp", 127 | "DuplicationController.h", 128 | "MainApplication.cpp", 129 | "MainApplication.h", 130 | "MainController.h", 131 | "MainThread.cpp", 132 | "MainThread.h", 133 | "Model.cpp", 134 | "Model.h", 135 | "OutputWindow.cpp", 136 | "OutputWindow.h", 137 | "RenderThread.cpp", 138 | "RenderThread.h", 139 | "TaskbarButtons.cpp", 140 | "TaskbarButtons.h", 141 | ] 142 | } 143 | files: [ 144 | "main.cpp", 145 | "main.ico", 146 | "main.manifest", 147 | "main.rc", 148 | ] 149 | } 150 | 151 | Group { 152 | name: "install" 153 | fileTagsFilter: "application" 154 | qbs.install: true 155 | } 156 | } 157 | 158 | Product { 159 | name: "Extra Files" 160 | builtByDefault: false 161 | 162 | files: [ 163 | ".clang-format", 164 | ".editorconfig", 165 | ".gitignore", 166 | "LICENSE", 167 | "README.adoc", 168 | ] 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/win32/TaskbarList.cpp: -------------------------------------------------------------------------------- 1 | #include "TaskbarList.h" 2 | 3 | #if __has_include() 4 | # include 5 | #endif 6 | 7 | namespace win32 { 8 | 9 | MenuEntry::MenuEntry(const MenuEntry &o) 10 | : icon(DuplicateIcon(nullptr, o.icon)) 11 | , bitmap(o.bitmap ? static_cast(CopyImage(o.bitmap, IMAGE_BITMAP, 0, 0, 0)) : HBITMAP{}) 12 | , tooltip(o.tooltip) 13 | , flags(o.flags) {} 14 | 15 | auto MenuEntry::operator=(const MenuEntry &o) -> MenuEntry & { 16 | if (icon) DestroyIcon(icon); 17 | if (bitmap) DeleteObject(bitmap); 18 | icon = o.icon ? DuplicateIcon(nullptr, o.icon) : HICON{}; 19 | bitmap = o.bitmap ? static_cast(CopyImage(o.bitmap, IMAGE_BITMAP, 0, 0, 0)) : HBITMAP{}; 20 | tooltip = o.tooltip; 21 | flags = o.flags; 22 | return *this; 23 | } 24 | 25 | TaskbarList::TaskbarList(HWND window, Config config) 26 | : m_window(window) 27 | , m_idBase(config.idBase) 28 | , m_iconSize(config.iconSize) { 29 | auto hr = CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(p.GetAddressOf())); 30 | if (SUCCEEDED(hr)) { 31 | p->HrInit(); 32 | } 33 | } 34 | 35 | auto TaskbarList::forWindow(HWND window) const noexcept -> TaskbarList { 36 | auto r = TaskbarList{}; 37 | r.m_window = window; 38 | r.p = p; 39 | return r; 40 | } 41 | 42 | void TaskbarList::setProgressFlags(ProgressFlags flags) { 43 | auto f = TBPFLAG{}; 44 | if (flags.has_any(ProgressFlag::Normal)) f |= TBPF_NORMAL; 45 | if (flags.has_any(ProgressFlag::Paused)) f |= TBPF_PAUSED; 46 | if (flags.has_any(ProgressFlag::Error)) f |= TBPF_ERROR; 47 | if (flags.has_any(ProgressFlag::Indeterminate)) f |= TBPF_INDETERMINATE; 48 | if (p) p->SetProgressState(m_window, f); 49 | } 50 | 51 | void TaskbarList::setProgressValue(uint64_t value, uint64_t total) { 52 | if (p) p->SetProgressValue(m_window, value, total); 53 | } 54 | 55 | void TaskbarList::setButtonLetterIcon(size_t idx, wchar_t chr, COLORREF Color) noexcept { 56 | const auto size = m_iconSize; 57 | constexpr const char fontName[] = "Segoe MDL2 Assets"; 58 | auto dc = GetDC(m_window); 59 | auto dcMem = CreateCompatibleDC(dc); 60 | 61 | auto bitmap = CreateCompatibleBitmap(dc, size, size); 62 | 63 | ReleaseDC(m_window, dc); 64 | DeleteDC(dc); 65 | 66 | const auto fontHeight = size; 67 | const auto fontWidth = 0; 68 | const auto escapement = 0; 69 | const auto orientation = 0; 70 | const auto weight = 900; 71 | const auto italic = false; 72 | const auto underline = false; 73 | const auto strikeOut = false; 74 | const auto charSet = 0u; 75 | const DWORD outputPrecision = OUT_TT_PRECIS; 76 | const DWORD clipPrecision = CLIP_DEFAULT_PRECIS; 77 | const DWORD quality = NONANTIALIASED_QUALITY; 78 | const DWORD pitchAndFamily = FF_DONTCARE; 79 | const auto font = CreateFontA( 80 | fontHeight, 81 | fontWidth, 82 | escapement, 83 | orientation, 84 | weight, 85 | italic, 86 | underline, 87 | strikeOut, 88 | charSet, 89 | outputPrecision, 90 | clipPrecision, 91 | quality, 92 | pitchAndFamily, 93 | fontName); 94 | const auto oldFont = SelectObject(dcMem, font); 95 | 96 | const auto oldBitmap = SelectObject(dcMem, bitmap); 97 | // SetDCBrushColor(dcMem, RGB(0, 0, 0)); 98 | // PatBlt(dcMem, 0, 0, size, size, PATCOPY); 99 | 100 | auto rect = RECT{0, 0, size, size}; 101 | const auto brush = CreateSolidBrush(RGB(0, 0, 0)); 102 | FillRect(dcMem, &rect, brush); 103 | 104 | const auto oldBkMode = SetBkMode(dcMem, TRANSPARENT); 105 | // auto oldBkColor = SetBkColor(dcMem, RGB(0, 0, 0)); 106 | const auto oldTextColor = SetTextColor(dcMem, Color); 107 | 108 | const UINT format = DT_NOCLIP | DT_CENTER | DT_SINGLELINE | DT_VCENTER; 109 | DrawTextW(dcMem, &chr, 1, &rect, format); 110 | 111 | SetBkMode(dcMem, oldBkMode); 112 | // SetBkColor(dcMem, oldBkColor); 113 | SetTextColor(dcMem, oldTextColor); 114 | SelectObject(dcMem, oldFont); 115 | SelectObject(dcMem, oldBitmap); 116 | DeleteObject(font); 117 | 118 | if (m_menu[idx].bitmap) DeleteObject(m_menu[idx].bitmap); 119 | m_menu[idx].bitmap = bitmap; 120 | m_imageUpdated = true; 121 | 122 | DeleteDC(dcMem); 123 | } 124 | 125 | void TaskbarList::setButtonFlags(size_t idx, ThumbButtonFlags flags) noexcept { 126 | if (idx < m_menu.size()) { 127 | m_menu[idx].flags = flags; 128 | } 129 | } 130 | 131 | void TaskbarList::setButtonTooltip(size_t idx, std::wstring str) { 132 | if (idx < m_menu.size()) { 133 | m_menu[idx].tooltip = std::move(str); 134 | } 135 | } 136 | 137 | static auto toWindowsFlags(ThumbButtonFlags flags) -> THUMBBUTTONFLAGS { 138 | THUMBBUTTONFLAGS f = THBF_ENABLED; 139 | if (flags.has_any(ThumbButtonFlag::Disabled)) f |= THBF_DISABLED; 140 | if (flags.has_any(ThumbButtonFlag::DismissOnClick)) f |= THBF_DISMISSONCLICK; 141 | if (flags.has_any(ThumbButtonFlag::NoBackground)) f |= THBF_NOBACKGROUND; 142 | if (flags.has_any(ThumbButtonFlag::Hidden)) f |= THBF_HIDDEN; 143 | if (flags.has_any(ThumbButtonFlag::NonInteractive)) f |= THBF_NONINTERACTIVE; 144 | return f; 145 | } 146 | 147 | void TaskbarList::updateThumbButtons() { 148 | if (m_imageUpdated) { 149 | updateThumbImages(); 150 | m_imageUpdated = false; 151 | } 152 | 153 | constexpr auto menuSize = 7u; 154 | auto menuData = std::array{}; 155 | auto i = 0u; 156 | for (auto &entry : m_menu) { 157 | [[gsl::suppress("26482")]] // menu and menuData have the same size 158 | auto &data = menuData[i]; 159 | data.iId = i + m_idBase; 160 | data.dwFlags = toWindowsFlags(entry.flags); 161 | data.dwMask = THB_FLAGS | THB_TOOLTIP; 162 | [[gsl::suppress("26485")]] // this is save 163 | wcscpy_s(data.szTip, ARRAYSIZE(data.szTip), entry.tooltip.c_str()); 164 | data.hIcon = entry.icon; 165 | if (entry.icon) data.dwMask |= THB_ICON; 166 | if (entry.bitmap) { 167 | data.iBitmap = i; 168 | data.dwMask |= THB_BITMAP; 169 | } 170 | i++; 171 | } 172 | 173 | if (!m_menuInitialized) { 174 | const auto hr = p->ThumbBarAddButtons(m_window, menuSize, menuData.data()); 175 | (void)hr; 176 | m_menuInitialized = true; 177 | } 178 | else { 179 | p->ThumbBarUpdateButtons(m_window, menuSize, menuData.data()); 180 | } 181 | } 182 | 183 | void TaskbarList::updateThumbImages() { 184 | auto imageList = ImageList_Create(m_iconSize, m_iconSize, ILC_MASK | ILC_COLOR32, 0, 8); 185 | for (auto &entry : m_menu) { 186 | ImageList_AddMasked(imageList, entry.bitmap, RGB(0, 0, 0)); 187 | } 188 | p->ThumbBarSetImageList(m_window, imageList); 189 | } 190 | 191 | } // namespace win32 192 | -------------------------------------------------------------------------------- /src/CaptureThread.cpp: -------------------------------------------------------------------------------- 1 | #include "CaptureThread.h" 2 | 3 | #include "CapturedUpdate.h" 4 | 5 | #include "meta/scope_guard.h" 6 | 7 | namespace { 8 | 9 | void setDesktop() { 10 | const auto flags = 0; 11 | const auto inherit = false; 12 | const auto access = GENERIC_ALL; 13 | auto desktop = OpenInputDesktop(flags, inherit, access); 14 | if (!desktop) throw Expected{"Failed to open desktop"}; 15 | LATER(CloseDesktop(desktop)); 16 | 17 | const auto result = SetThreadDesktop(desktop); 18 | if (!result) throw Expected{"Failed to set desktop"}; 19 | } 20 | 21 | } // namespace 22 | 23 | auto GetCurrentThreadHandle() -> HANDLE { 24 | HANDLE output{}; 25 | const auto process = GetCurrentProcess(); 26 | const auto thread = GetCurrentThread(); 27 | const auto desiredAccess = 0; 28 | const auto inheritHandle = false; 29 | const auto options = DUPLICATE_SAME_ACCESS; 30 | const auto success = DuplicateHandle(process, thread, process, &output, desiredAccess, inheritHandle, options); 31 | if (!success) throw Unexpected{"could not get thread handle"}; 32 | return output; 33 | } 34 | 35 | CaptureThread::~CaptureThread() { 36 | if (m_stdThread) stop(); 37 | } 38 | 39 | void CaptureThread::start(StartArgs &&args) { 40 | if (m_stdThread) return; // already started 41 | m_display = args.display; 42 | m_device = std::move(args.device); 43 | m_context.offset = args.offset; 44 | m_keepRunning = true; 45 | m_doCapture = true; 46 | m_stdThread.emplace([this] { run(); }); 47 | } 48 | 49 | void CaptureThread::next() { 50 | m_thread.queueUserApc([this]() { capture_next(); }); 51 | } 52 | 53 | void CaptureThread::stop() { 54 | m_thread.queueUserApc([this]() { capture_stop(); }); 55 | m_stdThread.reset(); 56 | } 57 | 58 | void CaptureThread::capture_next() { 59 | m_dupl->ReleaseFrame(); 60 | m_doCapture = true; 61 | } 62 | 63 | void CaptureThread::capture_stop() { m_keepRunning = false; } 64 | 65 | void CaptureThread::run() { 66 | m_thread = Thread::fromCurrent(); 67 | try { 68 | setDesktop(); 69 | initCapture(); 70 | while (m_keepRunning) { 71 | if (m_doCapture) { 72 | auto frame = captureUpdate(); 73 | if (frame) { 74 | m_config.setFrameCallback(m_config.callbackPtr, std::move(*frame), m_context, m_config.threadIndex); 75 | m_doCapture = false; 76 | } 77 | } 78 | const auto timeout = m_doCapture ? 1 : INFINITE; 79 | const auto alertable = true; 80 | SleepEx(timeout, alertable); 81 | } 82 | } 83 | catch (...) { 84 | m_config.setErrorCallback(m_config.callbackPtr, std::current_exception()); 85 | while (m_keepRunning) { 86 | const auto alertable = true; 87 | SleepEx(INFINITE, alertable); 88 | } 89 | } 90 | } 91 | 92 | void CaptureThread::initCapture() { 93 | auto dxgiDevice = ComPtr{}; 94 | auto dxResult = m_device.As(&dxgiDevice); 95 | if (IS_ERROR(dxResult)) throw Unexpected{"Failed to get IDXGIDevice from device"}; 96 | 97 | auto dxgiAdapter = ComPtr{}; 98 | dxResult = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), &dxgiAdapter); 99 | const auto parentExpected = {DXGI_ERROR_ACCESS_LOST, HRESULT{WAIT_ABANDONED}}; 100 | if (IS_ERROR(dxResult)) handleDeviceError("Failed to get IDXGIAdapter from device", dxResult, parentExpected); 101 | 102 | auto dxgiOutput = ComPtr{}; 103 | dxResult = dxgiAdapter->EnumOutputs(m_display, &dxgiOutput); 104 | if (IS_ERROR(dxResult)) handleDeviceError("Failed to get ouput from adapter", dxResult, {DXGI_ERROR_NOT_FOUND}); 105 | 106 | dxResult = dxgiOutput->GetDesc(&m_context.output_desc); 107 | if (IS_ERROR(dxResult)) handleDeviceError("Failed to get ouput description", dxResult, {}); 108 | 109 | auto dxgiOutput1 = ComPtr{}; 110 | dxResult = dxgiOutput.As(&dxgiOutput1); 111 | if (IS_ERROR(dxResult)) throw Unexpected{"Failed to get IDXGIOutput1 from dxgi_output"}; 112 | 113 | dxResult = dxgiOutput1->DuplicateOutput(m_device.Get(), &m_dupl); 114 | if (IS_ERROR(dxResult)) { 115 | if (DXGI_ERROR_NOT_CURRENTLY_AVAILABLE == dxResult) 116 | throw Unexpected{"Maximum of desktop duplications reached!"}; 117 | const auto duplicateExpected = { 118 | HRESULT{E_ACCESSDENIED}, 119 | DXGI_ERROR_UNSUPPORTED, 120 | DXGI_ERROR_SESSION_DISCONNECTED, 121 | }; 122 | handleDeviceError("Failed to get duplicate output from device", dxResult, duplicateExpected); 123 | } 124 | } 125 | 126 | void CaptureThread::handleDeviceError(const char *text, HRESULT result, std::initializer_list expected) { 127 | if (m_device) { 128 | const auto reason = m_device->GetDeviceRemovedReason(); 129 | if (S_OK != reason) throw Expected{text}; 130 | } 131 | for (const auto cand : expected) { 132 | if (result == cand) throw Expected{text}; 133 | } 134 | throw Unexpected{text}; 135 | } 136 | 137 | auto CaptureThread::captureUpdate() -> std::optional { 138 | auto result = std::optional{}; 139 | const auto time = 10; 140 | auto resource = ComPtr{}; 141 | auto frameInfo = DXGI_OUTDUPL_FRAME_INFO{}; 142 | auto dxResult = m_dupl->AcquireNextFrame(time, &frameInfo, &resource); 143 | if (DXGI_ERROR_WAIT_TIMEOUT == dxResult) return {}; 144 | if (IS_ERROR(dxResult)) throw Expected{"Failed to acquire next frame in capture_thread"}; 145 | 146 | result.emplace(); 147 | auto &update = result.value(); 148 | update.frame.frames = frameInfo.AccumulatedFrames; 149 | update.frame.present_time = frameInfo.LastPresentTime.QuadPart; 150 | update.frame.rects_coalesced = frameInfo.RectsCoalesced; 151 | update.frame.protected_content_masked_out = frameInfo.ProtectedContentMaskedOut; 152 | 153 | if (0 != frameInfo.TotalMetadataBufferSize) { 154 | update.frame.buffer.resize(frameInfo.TotalMetadataBufferSize); 155 | 156 | auto movedPtr = update.frame.buffer.data(); 157 | dxResult = m_dupl->GetFrameMoveRects( 158 | frameInfo.TotalMetadataBufferSize, 159 | std::bit_cast(movedPtr), 160 | &update.frame.moved_bytes); 161 | if (IS_ERROR(dxResult)) throw Expected{"Failed to get frame moved rects in capture_thread"}; 162 | 163 | auto dirtyPtr = movedPtr + update.frame.moved_bytes; 164 | const auto dirtySize = frameInfo.TotalMetadataBufferSize - update.frame.moved_bytes; 165 | dxResult = m_dupl->GetFrameDirtyRects(dirtySize, std::bit_cast(dirtyPtr), &update.frame.dirty_bytes); 166 | if (IS_ERROR(dxResult)) throw Expected{"Failed to get frame dirty rects in capture_thread"}; 167 | } 168 | if (!update.frame.dirty().empty()) { 169 | dxResult = resource.As(&update.frame.image); 170 | if (IS_ERROR(dxResult)) throw Unexpected{"Failed to get ID3D11Texture from resource in capture_thread"}; 171 | } 172 | 173 | update.pointer.update_time = frameInfo.LastMouseUpdateTime.QuadPart; 174 | update.pointer.position = frameInfo.PointerPosition; 175 | if (0 != frameInfo.PointerShapeBufferSize) { 176 | update.pointer.shape_buffer.resize(frameInfo.PointerShapeBufferSize); 177 | 178 | auto pointerPtr = update.pointer.shape_buffer.data(); 179 | const auto pointerSize = static_cast(update.pointer.shape_buffer.size()); 180 | auto sizeRequiredDummy = UINT{}; 181 | dxResult = 182 | m_dupl->GetFramePointerShape(pointerSize, pointerPtr, &sizeRequiredDummy, &update.pointer.shape_info); 183 | if (IS_ERROR(dxResult)) throw Expected{"Failed to get frame pointer shape in capture_thread"}; 184 | // assert(size_required_dummy == frame.pointer_data.size()); 185 | } 186 | return update; 187 | } 188 | -------------------------------------------------------------------------------- /src/win32/Window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Dpi.h" 3 | #include "Geometry.h" 4 | #include "WindowMessageHandler.h" 5 | 6 | #include "meta/member_method.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace win32 { 12 | 13 | using Name = std::wstring; 14 | using String = std::wstring; 15 | 16 | struct Icon { 17 | HICON hIcon{}; 18 | }; 19 | 20 | struct Cursor { 21 | HCURSOR hCursor{}; 22 | }; 23 | 24 | struct Instance { 25 | HINSTANCE hInstance{}; 26 | }; 27 | 28 | struct Menu { 29 | HMENU hMenu{}; 30 | }; 31 | 32 | /// encapsulates normal and exteded window styles in one 33 | struct WindowStyle { 34 | DWORD exStyle{}; 35 | DWORD style{}; 36 | 37 | static auto child() -> WindowStyle { return {0, WS_CHILD}; } 38 | static auto popup() -> WindowStyle { return {WS_EX_TOOLWINDOW, WS_POPUP}; } 39 | static auto overlappedWindow() -> WindowStyle { return {WS_EX_OVERLAPPEDWINDOW, WS_OVERLAPPEDWINDOW}; } 40 | 41 | auto topMost() const -> WindowStyle { return {exStyle | WS_EX_TOPMOST, style}; } 42 | auto transparent() const -> WindowStyle { return {exStyle | WS_EX_TRANSPARENT, style}; } 43 | }; 44 | 45 | /// convinient wrapper to ease working with windows 46 | struct Window { 47 | enum class ShowState { Normal, Minimized, Maximized }; 48 | struct Placement { 49 | ShowState showState{}; 50 | Rect currentRect{}; 51 | Rect normalRect{}; 52 | }; 53 | /// Configuration used for construction of new windows 54 | /// use with WindowClass::createWindow 55 | struct Config { 56 | WindowStyle style = {}; 57 | Name name = {}; 58 | Rect rect = {}; 59 | const Window *parent = {}; 60 | Menu menu = {}; 61 | }; 62 | 63 | explicit Window() = default; ///< default constructs invalid window 64 | explicit Window(HWND hwnd) 65 | : m_windowHandle(hwnd) {} 66 | 67 | /// construct a Window wrapper for the current foreground window 68 | static auto fromForeground() -> Window { return Window{::GetForegroundWindow()}; } 69 | 70 | /// construct a Window wrapper for the desktop window 71 | static auto fromDesktop() -> Window { return Window{::GetDesktopWindow()}; } 72 | 73 | /// calls f with all windows 74 | // note: this is a template - therefore it is kept inline! 75 | template 76 | static void enumerate(F &&f) { 77 | struct Callback { 78 | static auto CALLBACK enumerateCallback(HWND hwnd, LPARAM lParam) -> BOOL { 79 | auto *fp = std::bit_cast(lParam); 80 | (*fp)(Window{hwnd}); 81 | return true; 82 | } 83 | }; 84 | ::EnumWindows(&Callback::enumerateCallback, std::bit_cast(&f)); 85 | } 86 | 87 | /// returns the internal handle 88 | /// use it to call non wrapped win32 APIs 89 | auto handle() const -> HWND { return m_windowHandle; } 90 | 91 | auto rect() const -> Rect; ///< returns outer window rect 92 | // note: requires extra dwn dll - enable if needed. 93 | // auto extendedFrameRect() const -> Rect; ///< returns border rect 94 | auto clientRect() const -> Rect; 95 | auto clientOffset() const -> Point; 96 | 97 | /// store current window placement 98 | auto placement() const -> Placement; 99 | auto isMaximized() const -> bool; 100 | auto isMinimized() const -> bool; 101 | 102 | auto style() const -> WindowStyle; 103 | auto isVisible() const -> bool; ///< true if Style has WS_VISIBLE (intended to draw) 104 | auto dpiAwareness() const -> DpiAwareness; 105 | auto dpi() const -> Dpi; 106 | 107 | /// return title or input text of window 108 | auto text() const -> String; 109 | 110 | void show(); ///< send show command to window (last state is kept) 111 | void hide(); ///< send hide command (will window will disapear) 112 | void toBottom(); 113 | void toTop(); 114 | 115 | void showNormal(); ///< send show normal (restores from maximized) 116 | void showMaximized(); ///< send show maximized command 117 | void showMinimized(); ///< send show minimized command 118 | 119 | void update(); ///< trigger WM_PAINT for window 120 | 121 | // note: some app windows may not follow the move/resize! 122 | void move(Rect); ///< move outer window rect 123 | void moveBorder(Rect); ///< move window visible border to rect 124 | void moveClient(Rect); ///< move window client rect 125 | void setPosition(Point); ///< move top left corner (keeping size) 126 | bool setPlacement(const Placement &); ///< restore a placement 127 | 128 | void styleNonLayered(); 129 | void styleTransparent(); 130 | void styleLayeredColorKey(uint32_t colorKey); 131 | void styleLayeredAlpha(uint8_t alpha); 132 | 133 | private: 134 | friend struct WindowWithMessages; 135 | HWND m_windowHandle{}; 136 | }; 137 | 138 | /// Extended Wrapper for our own created windows 139 | /// note: 140 | /// * constructed by WindowClass::createWindow 141 | /// * allows to set the message handler for this window 142 | struct WindowWithMessages final : Window { 143 | WindowWithMessages(const WindowWithMessages &) = delete; 144 | WindowWithMessages &operator=(const WindowWithMessages &) = delete; 145 | WindowWithMessages(WindowWithMessages &&) = delete; 146 | WindowWithMessages &operator=(WindowWithMessages &&) = delete; 147 | ~WindowWithMessages() noexcept; ///< destroy the owned window 148 | 149 | /// defines the proper message handler 150 | template 151 | void setMessageHandler(WindowMessageHandler *handler) { 152 | m_messageHandlerFunc = &handler->handleMessage; 153 | m_messageHandlerPtr = handler; 154 | } 155 | template> 156 | requires(MemberMedthodSig) 157 | void setCustomHandler(T *ptr) { 158 | m_messageHandlerFunc = [](void *ptr, const WindowMessage &msg) -> OptLRESULT { 159 | auto p = std::bit_cast(ptr); 160 | return (p->*M)(msg); 161 | }; 162 | m_messageHandlerPtr = ptr; 163 | } 164 | /// restore the noop message handler 165 | void resetMessageHandler(); 166 | 167 | auto handleMessage(const WindowMessage &msg) -> OptLRESULT; 168 | 169 | private: 170 | using HandleFunc = auto(void *, const WindowMessage &) -> OptLRESULT; 171 | static auto noopMessageHandler(void *, const WindowMessage &) -> OptLRESULT; 172 | 173 | HandleFunc *m_messageHandlerFunc{&WindowWithMessages::noopMessageHandler}; 174 | void *m_messageHandlerPtr{}; 175 | 176 | friend struct WindowClass; 177 | WindowWithMessages(const Config &config, HINSTANCE instance, const Name &className); 178 | }; 179 | 180 | /// Wrapper around our created WindowClass 181 | struct WindowClass final { 182 | /// configuration to construct our windowClass 183 | /// note: avoids long argument lists 184 | struct Config { 185 | /// current application instance (required) 186 | /// note: you get it from WinMain or ::GetModuleHandle(nullptr) 187 | HINSTANCE instanceHandle = {}; 188 | Icon icon = {}; 189 | Icon smallIcon = {}; 190 | Cursor cursor = {}; 191 | Name className = {}; ///< class name used for WindowClass (required) 192 | Name menuName = {}; 193 | 194 | bool isValid() const { return instanceHandle != nullptr && !className.empty(); } 195 | }; 196 | WindowClass(Config); 197 | ~WindowClass() noexcept; 198 | 199 | /// instance handle used to create WindowClass 200 | /// note: use to call win32 APIs that need it 201 | auto instanceHandle() const { return m_instanceHandle; } 202 | 203 | /// construct a new window according to the provided config 204 | /// note: you should set a message handler and probably show the window 205 | auto createWindow(WindowWithMessages::Config) const -> WindowWithMessages; 206 | 207 | void recreateWindow(WindowWithMessages &wnd, WindowWithMessages::Config) const; 208 | 209 | private: 210 | HINSTANCE m_instanceHandle{}; 211 | Name m_className{}; 212 | 213 | static auto registerWindowClass(const Config &config) -> ATOM; 214 | ATOM m_atom{}; 215 | 216 | static auto CALLBACK staticWindowProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam) -> LRESULT; 217 | }; 218 | 219 | } // namespace win32 220 | -------------------------------------------------------------------------------- /src/FrameUpdater.cpp: -------------------------------------------------------------------------------- 1 | #include "FrameUpdater.h" 2 | 3 | #include "renderer.h" 4 | 5 | #include "CapturedUpdate.h" 6 | #include "FrameContext.h" 7 | 8 | namespace { 9 | 10 | using win32::Dimension; 11 | using win32::Point; 12 | using win32::Rect; 13 | 14 | auto rotate(Rect rect, DXGI_MODE_ROTATION rotation, Dimension spaceDim) noexcept -> Rect { 15 | switch (rotation) { 16 | case DXGI_MODE_ROTATION_UNSPECIFIED: 17 | case DXGI_MODE_ROTATION_IDENTITY: return rect; 18 | case DXGI_MODE_ROTATION_ROTATE90: 19 | return { 20 | Point{spaceDim.height - rect.bottom(), rect.left()}, 21 | Dimension{rect.height(), rect.width()}, 22 | }; 23 | case DXGI_MODE_ROTATION_ROTATE180: 24 | return { 25 | Point{spaceDim.width - rect.right(), spaceDim.height - rect.bottom()}, 26 | rect.dimension, 27 | }; 28 | case DXGI_MODE_ROTATION_ROTATE270: 29 | return { 30 | Point{rect.top(), spaceDim.width - rect.right()}, 31 | Dimension{rect.height(), rect.width()}, 32 | }; 33 | default: return {}; 34 | } 35 | } 36 | 37 | } // namespace 38 | 39 | FrameUpdater::FrameUpdater(InitArgs &&args) 40 | : m_dx(std::move(args)) {} 41 | 42 | void FrameUpdater::update(const FrameUpdate &data, const FrameContext &context) { 43 | performMoves(data, context); 44 | updateDirty(data, context); 45 | } 46 | 47 | void FrameUpdater::performMoves(const FrameUpdate &data, const FrameContext &context) { 48 | const auto moved = data.moved(); 49 | if (moved.empty()) return; 50 | 51 | const auto desktopRect = Rect::fromRECT(context.output_desc.DesktopCoordinates); 52 | const auto &desktopDim = desktopRect.dimension; 53 | 54 | if (!m_dx.moveTmp) { 55 | auto target_description = D3D11_TEXTURE2D_DESC{}; 56 | m_dx.target->GetDesc(&target_description); 57 | 58 | auto move_description = D3D11_TEXTURE2D_DESC{ 59 | .Width = static_cast(desktopDim.width), 60 | .Height = static_cast(desktopDim.height), 61 | .MipLevels = 1, 62 | .ArraySize = target_description.ArraySize, 63 | .Format = target_description.Format, 64 | .SampleDesc = target_description.SampleDesc, 65 | .Usage = target_description.Usage, 66 | .BindFlags = D3D11_BIND_RENDER_TARGET, 67 | .CPUAccessFlags = target_description.CPUAccessFlags, 68 | .MiscFlags = 0, 69 | }; 70 | const auto result = m_dx.device()->CreateTexture2D(&move_description, nullptr, &m_dx.moveTmp); 71 | if (IS_ERROR(result)) throw RenderFailure(result, "Failed to create move temporary texture"); 72 | } 73 | 74 | const auto target_x = context.output_desc.DesktopCoordinates.left - context.offset.x; 75 | const auto target_y = context.output_desc.DesktopCoordinates.top - context.offset.y; 76 | const auto rotation = context.output_desc.Rotation; 77 | 78 | for (auto &move : moved) { 79 | auto moveDestinationRect = Rect::fromRECT(move.DestinationRect); 80 | auto moveSourcePoint = Point::fromPOINT(move.SourcePoint); 81 | 82 | const auto sourceRect = rotate(Rect{moveSourcePoint, moveDestinationRect.dimension}, rotation, desktopDim); 83 | const auto dest = rotate(moveDestinationRect, rotation, desktopDim); 84 | 85 | auto box = D3D11_BOX{ 86 | .left = static_cast(sourceRect.left() + target_x), 87 | .top = static_cast(sourceRect.top() + target_y), 88 | .front = 0, 89 | .right = static_cast(sourceRect.right() + target_x), 90 | .bottom = static_cast(sourceRect.bottom() + target_y), 91 | .back = 1, 92 | }; 93 | 94 | m_dx.deviceContext()->CopySubresourceRegion( 95 | m_dx.moveTmp.Get(), 0, sourceRect.left(), sourceRect.top(), 0, m_dx.target.Get(), 0, &box); 96 | 97 | box.left = sourceRect.left(); 98 | box.top = sourceRect.top(); 99 | box.right = sourceRect.right(); 100 | box.bottom = sourceRect.bottom(); 101 | 102 | m_dx.deviceContext()->CopySubresourceRegion( 103 | m_dx.target.Get(), 0, dest.left() + target_x, dest.top() + target_y, 0, m_dx.moveTmp.Get(), 0, &box); 104 | } 105 | } 106 | 107 | void FrameUpdater::updateDirty(const FrameUpdate &data, const FrameContext &context) { 108 | auto dirts = data.dirty(); 109 | if (dirts.empty()) return; 110 | 111 | const auto desktop = data.image.Get(); 112 | 113 | ComPtr shader_resource = m_dx.createShaderTexture(desktop); 114 | m_dx.deviceContext()->PSSetShaderResources(0, 1, shader_resource.GetAddressOf()); 115 | 116 | auto target_description = D3D11_TEXTURE2D_DESC{}; 117 | m_dx.target->GetDesc(&target_description); 118 | 119 | auto desktop_description = D3D11_TEXTURE2D_DESC{}; 120 | desktop->GetDesc(&desktop_description); 121 | 122 | const auto target_x = context.output_desc.DesktopCoordinates.left - context.offset.x; 123 | const auto target_y = context.output_desc.DesktopCoordinates.top - context.offset.y; 124 | const auto desktopRect = Rect::fromRECT(context.output_desc.DesktopCoordinates); 125 | const auto rotation = context.output_desc.Rotation; 126 | 127 | const auto center_x = static_cast(target_description.Width) / 2; 128 | const auto center_y = static_cast(target_description.Height) / 2; 129 | 130 | const auto make_vertex = [&](int x, int y) { 131 | return Vertex{ 132 | (x + target_x - center_x) / static_cast(center_x), 133 | -1 * (y + target_y - center_y) / static_cast(center_y), 134 | x / static_cast(desktop_description.Width), 135 | y / static_cast(desktop_description.Height)}; 136 | }; 137 | 138 | auto vertexBufferSize = static_cast(sizeof(quad_vertices) * dirts.size()); 139 | if (m_dx.vertexBufferSize < vertexBufferSize) { 140 | auto buffer_description = D3D11_BUFFER_DESC{ 141 | .ByteWidth = vertexBufferSize, 142 | .Usage = D3D11_USAGE_DYNAMIC, 143 | .BindFlags = D3D11_BIND_VERTEX_BUFFER, 144 | .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE, 145 | .MiscFlags = {}, 146 | .StructureByteStride = {}, 147 | }; 148 | const auto result = m_dx.device()->CreateBuffer(&buffer_description, nullptr, &m_dx.vertexBuffer); 149 | if (IS_ERROR(result)) throw RenderFailure(result, "Failed to create dirty vertex buffer"); 150 | m_dx.vertexBufferSize = vertexBufferSize; 151 | } 152 | 153 | auto mapped = D3D11_MAPPED_SUBRESOURCE{}; 154 | const auto subresource = 0; 155 | const auto mapType = D3D11_MAP_WRITE_DISCARD; 156 | const unsigned mapFlags = 0; 157 | m_dx.deviceContext()->Map(m_dx.vertexBuffer.Get(), subresource, mapType, mapFlags, &mapped); 158 | 159 | auto *vertex_ptr = static_cast(mapped.pData); 160 | 161 | for (const auto &dirt : dirts) { 162 | auto dirtRect = Rect::fromRECT(dirt); 163 | const auto rotated = rotate(dirtRect, rotation, desktopRect.dimension); 164 | auto &vertices = *vertex_ptr; 165 | 166 | vertices[0] = make_vertex(rotated.left(), rotated.bottom()); 167 | vertices[1] = make_vertex(rotated.left(), rotated.top()); 168 | vertices[2] = make_vertex(rotated.right(), rotated.bottom()); 169 | vertices[3] = vertices[2]; 170 | vertices[4] = vertices[1]; 171 | vertices[5] = make_vertex(rotated.right(), rotated.top()); 172 | vertex_ptr++; 173 | } 174 | m_dx.deviceContext()->Unmap(m_dx.vertexBuffer.Get(), 0); 175 | 176 | const uint32_t stride = sizeof(Vertex); 177 | const uint32_t offset = 0; 178 | m_dx.deviceContext()->IASetVertexBuffers(0, 1, m_dx.vertexBuffer.GetAddressOf(), &stride, &offset); 179 | 180 | auto view_port = D3D11_VIEWPORT{ 181 | .TopLeftX = 0.f, 182 | .TopLeftY = 0.f, 183 | .Width = static_cast(target_description.Width), 184 | .Height = static_cast(target_description.Height), 185 | .MinDepth = 0.f, 186 | .MaxDepth = 1.f, 187 | }; 188 | m_dx.deviceContext()->RSSetViewports(1, &view_port); 189 | 190 | [[gsl::suppress("26472")]] // conversion required because of APIs 191 | m_dx.deviceContext() 192 | ->Draw(static_cast(6 * dirts.size()), 0); 193 | 194 | // dx_m.activateNoRenderTarget(); 195 | ID3D11ShaderResourceView *noResource = nullptr; 196 | m_dx.deviceContext()->PSSetShaderResources(0, 1, &noResource); 197 | } 198 | 199 | FrameUpdater::Resources::Resources(FrameUpdater::InitArgs &&args) 200 | : BaseRenderer(std::move(args)) { 201 | prepare(args.targetHandle); 202 | 203 | activateRenderTarget(); 204 | activateTriangleList(); 205 | 206 | activateVertexShader(); 207 | activatePlainPixelShader(); 208 | activateDiscreteSampler(); 209 | } 210 | 211 | void FrameUpdater::Resources::prepare(HANDLE targetHandle) { 212 | target = renderer::getTextureFromHandle(device(), targetHandle); 213 | renderTarget = renderer::renderToTexture(device(), target); 214 | } 215 | -------------------------------------------------------------------------------- /src/renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "renderer.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace renderer { 7 | 8 | auto createDevice() -> DeviceData { 9 | auto dev = DeviceData{}; 10 | 11 | constexpr auto driver_types = std::array{ 12 | D3D_DRIVER_TYPE_HARDWARE, 13 | D3D_DRIVER_TYPE_WARP, 14 | D3D_DRIVER_TYPE_REFERENCE, 15 | }; 16 | auto const feature_levels = std::array{ 17 | D3D_FEATURE_LEVEL_11_0, 18 | D3D_FEATURE_LEVEL_10_1, 19 | D3D_FEATURE_LEVEL_10_0, 20 | D3D_FEATURE_LEVEL_9_1, 21 | }; 22 | auto result = HRESULT{}; 23 | for (auto driver_type : driver_types) { 24 | static constexpr auto const adapter = nullptr; 25 | static constexpr auto const software = nullptr; 26 | auto const flags = 0; // D3D11_CREATE_DEVICE_DEBUG; 27 | result = D3D11CreateDevice( 28 | adapter, 29 | driver_type, 30 | software, 31 | flags, 32 | feature_levels.data(), 33 | static_cast(feature_levels.size()), 34 | D3D11_SDK_VERSION, 35 | &dev.device, 36 | &dev.selectedFeatureLevel, 37 | &dev.deviceContext); 38 | if (SUCCEEDED(result)) break; 39 | } 40 | if (IS_ERROR(result)) throw Error{result, "Failed to create device"}; 41 | return dev; 42 | } 43 | 44 | auto getFactory(const ComPtr &device) -> ComPtr { 45 | [[gsl::suppress("26415"), gsl::suppress("26418")]] // ComPtr is not just a smart pointer 46 | auto dxgi_device = ComPtr{}; 47 | { 48 | auto const result = device.As(&dxgi_device); 49 | if (IS_ERROR(result)) throw Error{result, "Failed to get IDXGIDevice from device"}; 50 | } 51 | auto dxgi_adapter = ComPtr{}; 52 | { 53 | auto const result = dxgi_device->GetParent(__uuidof(IDXGIAdapter), &dxgi_adapter); 54 | if (IS_ERROR(result)) throw Error{result, "Failed to get IDXGIAdapter from device"}; 55 | } 56 | auto dxgi_factory = ComPtr{}; 57 | { 58 | auto const result = dxgi_adapter->GetParent(__uuidof(IDXGIFactory2), &dxgi_factory); 59 | if (IS_ERROR(result)) throw Error{result, "Failed to get IDXGIFactory2 from adapter"}; 60 | } 61 | return dxgi_factory; 62 | } 63 | 64 | auto createSwapChain(const ComPtr &factory, const ComPtr &device, HWND window) 65 | -> ComPtr { 66 | [[gsl::suppress("26415"), gsl::suppress("26418")]] // ComPtr is not just a smart pointer 67 | auto rect = RECT{}; 68 | ::GetClientRect(window, &rect); 69 | auto const width = static_cast(rect.right - rect.left); 70 | auto const height = static_cast(rect.bottom - rect.top); 71 | 72 | auto swap_chain_desription = DXGI_SWAP_CHAIN_DESC1{ 73 | .Width = width, 74 | .Height = height, 75 | .Format = DXGI_FORMAT_B8G8R8A8_UNORM, 76 | .Stereo = {}, 77 | .SampleDesc = DXGI_SAMPLE_DESC{.Count = 1, .Quality = 0}, 78 | .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT, 79 | .BufferCount = 2, 80 | .Scaling = DXGI_SCALING_NONE, 81 | .SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, 82 | .AlphaMode = {}, 83 | .Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT, 84 | }; 85 | auto swap_chain = ComPtr{}; 86 | { 87 | auto const result = factory->CreateSwapChainForHwnd( 88 | device.Get(), window, &swap_chain_desription, nullptr, nullptr, &swap_chain); 89 | if (IS_ERROR(result)) throw Error{result, "Failed to create window swapchain"}; 90 | } 91 | { 92 | auto const result = factory->MakeWindowAssociation(window, DXGI_MWA_NO_ALT_ENTER); 93 | if (IS_ERROR(result)) throw Error{result, "Failed to associate window"}; 94 | } 95 | auto swap_chain2 = ComPtr{}; 96 | { 97 | auto const result = swap_chain.As(&swap_chain2); 98 | if (IS_ERROR(result)) throw Error{result, "Failed to get swapchain2"}; 99 | } 100 | return swap_chain2; 101 | } 102 | 103 | auto getDimensionData(const ComPtr &device, const std::vector &displays) -> DimensionData { 104 | [[gsl::suppress("26415"), gsl::suppress("26418")]] // ComPtr is not just a smart pointer 105 | auto dxgi_device = ComPtr{}; 106 | { 107 | auto const result = device.As(&dxgi_device); 108 | if (IS_ERROR(result)) throw Error{result, "Failed to get IDXGIDevice from device"}; 109 | } 110 | auto dxgi_adapter = ComPtr{}; 111 | { 112 | auto const result = dxgi_device->GetParent(__uuidof(IDXGIAdapter), &dxgi_adapter); 113 | if (IS_ERROR(result)) throw Error{result, "Failed to get IDXGIAdapter from device"}; 114 | } 115 | auto rect = RECT{}; 116 | rect.top = rect.left = std::numeric_limits::max(); 117 | rect.bottom = rect.right = std::numeric_limits::min(); 118 | 119 | auto output = DimensionData{}; 120 | for (auto display : displays) { 121 | auto dxgi_output = ComPtr{}; 122 | { 123 | auto const result = dxgi_adapter->EnumOutputs(display, &dxgi_output); 124 | if (DXGI_ERROR_NOT_FOUND == result) continue; 125 | if (IS_ERROR(result)) throw Error{result, "Failed to enumerate Output"}; 126 | } 127 | auto description = DXGI_OUTPUT_DESC{}; 128 | dxgi_output->GetDesc(&description); 129 | 130 | rect.top = std::min(rect.top, description.DesktopCoordinates.top); 131 | rect.left = std::min(rect.left, description.DesktopCoordinates.left); 132 | rect.bottom = std::max(rect.bottom, description.DesktopCoordinates.bottom); 133 | rect.right = std::max(rect.right, description.DesktopCoordinates.right); 134 | output.used_displays.push_back(display); 135 | } 136 | if (output.used_displays.empty()) throw Error{HRESULT{}, "Found no valid displays"}; 137 | 138 | output.rect = Rect::fromRECT(rect); 139 | return output; 140 | } 141 | 142 | auto createTexture(const ComPtr &device, Dimension dimension) -> ComPtr { 143 | [[gsl::suppress("26415"), gsl::suppress("26418")]] // ComPtr is not just a smart pointer 144 | auto const description = D3D11_TEXTURE2D_DESC{ 145 | .Width = static_cast(dimension.width), 146 | .Height = static_cast(dimension.height), 147 | .MipLevels = 1, 148 | .ArraySize = 1, 149 | .Format = DXGI_FORMAT_B8G8R8A8_UNORM, 150 | .SampleDesc = DXGI_SAMPLE_DESC{.Count = 1, .Quality = {}}, 151 | .Usage = D3D11_USAGE_DEFAULT, 152 | .BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE, 153 | .CPUAccessFlags = 0, 154 | .MiscFlags = {}, // D3D11_RESOURCE_MISC_SHARED 155 | }; 156 | auto texture = ComPtr{}; 157 | { 158 | auto const result = device->CreateTexture2D(&description, nullptr, &texture); 159 | if (IS_ERROR(result)) throw Error{result, "Failed to create texture"}; 160 | } 161 | return texture; 162 | } 163 | 164 | auto createSharedTexture(const ComPtr &device, Dimension dimension) -> ComPtr { 165 | [[gsl::suppress("26415"), gsl::suppress("26418")]] // ComPtr is not just a smart pointer 166 | auto const description = D3D11_TEXTURE2D_DESC{ 167 | .Width = static_cast(dimension.width), 168 | .Height = static_cast(dimension.height), 169 | .MipLevels = 1, 170 | .ArraySize = 1, 171 | .Format = DXGI_FORMAT_B8G8R8A8_UNORM, 172 | .SampleDesc = DXGI_SAMPLE_DESC{.Count = 1, .Quality = {}}, 173 | .Usage = D3D11_USAGE_DEFAULT, 174 | .BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE, 175 | .CPUAccessFlags = 0, 176 | .MiscFlags = D3D11_RESOURCE_MISC_SHARED, // | D3D11_RESOURCE_MISC_GENERATE_MIPS 177 | }; 178 | auto texture = ComPtr{}; 179 | { 180 | auto const result = device->CreateTexture2D(&description, nullptr, &texture); 181 | if (IS_ERROR(result)) throw Error{result, "Failed to create shared texture"}; 182 | } 183 | return texture; 184 | } 185 | 186 | auto getSharedHandle(const ComPtr &texture) -> HANDLE { 187 | [[gsl::suppress("26415"), gsl::suppress("26418")]] // ComPtr is not just a smart pointer 188 | auto DXGIResource = ComPtr{}; 189 | { 190 | auto const result = texture.As(&DXGIResource); 191 | if (IS_ERROR(result)) throw Error{result, "Failed to convert shared texture"}; 192 | } 193 | auto handle = HANDLE{}; 194 | { 195 | auto const result = DXGIResource->GetSharedHandle(&handle); 196 | if (IS_ERROR(result)) throw Error{result, "Failed to retrieve handle"}; 197 | } 198 | return handle; 199 | } 200 | 201 | auto getTextureFromHandle(const ComPtr &device, HANDLE handle) -> ComPtr { 202 | [[gsl::suppress("26415"), gsl::suppress("26418")]] // ComPtr is not just a smart pointer 203 | auto output = ComPtr{}; 204 | { 205 | auto const result = device->OpenSharedResource(handle, __uuidof(ID3D11Texture2D), &output); 206 | if (IS_ERROR(result)) throw Error{result, "Failed to fetch texture from handle"}; 207 | } 208 | return output; 209 | } 210 | 211 | auto renderToTexture(const ComPtr &device, const ComPtr &texture) 212 | -> ComPtr { 213 | [[gsl::suppress("26415"), gsl::suppress("26418")]] // ComPtr is not just a smart pointer 214 | auto output = ComPtr{}; 215 | static constexpr const auto render_target_description = nullptr; 216 | { 217 | auto const result = device->CreateRenderTargetView(texture.Get(), render_target_description, &output); 218 | if (IS_ERROR(result)) throw Error{result, "Failed to create render target to texture"}; 219 | } 220 | return output; 221 | } 222 | 223 | } // namespace renderer 224 | -------------------------------------------------------------------------------- /src/DuplicationController.cpp: -------------------------------------------------------------------------------- 1 | #include "DuplicationController.h" 2 | #include "CapturedUpdate.h" 3 | #include "FrameUpdater.h" 4 | #include "Model.h" 5 | #include "renderer.h" 6 | 7 | namespace deskdup { 8 | namespace { 9 | 10 | auto createRetryTimer() -> WaitableTimer { 11 | auto config = WaitableTimer::Config{}; 12 | config.timerName = TEXT("Local:RetryTimer"); 13 | return WaitableTimer{config}; 14 | } 15 | 16 | auto createWindowConfig(const Window &parent, Dimension dim) -> win32::WindowWithMessages::Config { 17 | return { 18 | .style = win32::WindowStyle::child(), 19 | .name = win32::Name{L"Hello"}, 20 | .rect = Rect{.topLeft = {}, .dimension = dim}, 21 | .parent = &parent, 22 | }; 23 | } 24 | 25 | } // namespace 26 | 27 | DuplicationController::DuplicationController(const Args &args) 28 | : m_windowClass{args.windowClass} 29 | , m_controller{args.mainController} 30 | , m_mainThread{args.mainThread} 31 | , m_outputWindow{args.outputWindow} 32 | , m_renderWindow{m_windowClass.createWindow( 33 | createWindowConfig(m_outputWindow, m_controller.config().outputRect().dimension))} 34 | , m_renderThread{renderThreadConfig(m_controller.operatonModeLens())} 35 | , m_captureThread{captureThreadConfig()} 36 | , m_retryTimer{createRetryTimer()} { 37 | m_renderWindow.setCustomHandler<&DuplicationController::forwardRenderMessage>(this); 38 | m_renderThread.start(); 39 | } 40 | 41 | DuplicationController::~DuplicationController() { resetOnMain(); } 42 | 43 | void DuplicationController::updateDuplicationStatus(DuplicationStatus status) { 44 | using enum DuplicationStatus; 45 | switch (status) { 46 | case Live: return startOnMain(); 47 | case Pause: return pauseOnMain(); 48 | } 49 | } 50 | 51 | void DuplicationController::updateOutputDimension(Dimension dimension) { 52 | m_renderWindow.moveClient(Rect{.topLeft = {}, .dimension = dimension}); 53 | m_renderThread.thread().queueUserApc([this, dimension]() { 54 | m_renderThread.windowRenderer().resize(dimension); 55 | m_renderThread.updated(); 56 | }); 57 | if (m_controller.state().duplicationStatus != DuplicationStatus::Live) { 58 | restart(); 59 | } 60 | } 61 | 62 | void DuplicationController::updateOutputZoom(float zoom) { 63 | m_renderThread.thread().queueUserApc([this, zoom]() { 64 | m_renderThread.windowRenderer().zoomOutput(zoom); 65 | m_renderThread.updated(); 66 | }); 67 | } 68 | 69 | void DuplicationController::updateCaptureOffset(Vec2f offset) { 70 | m_renderThread.thread().queueUserApc([this, offset]() { m_renderThread.windowRenderer().updateOffset(offset); }); 71 | if (m_controller.state().duplicationStatus != DuplicationStatus::Live) { 72 | restart(); 73 | } 74 | } 75 | 76 | void DuplicationController::restart() { 77 | stopOnMain(); 78 | if (m_controller.state().duplicationStatus == DuplicationStatus::Live) { 79 | startOnMain(); 80 | } 81 | } 82 | 83 | auto DuplicationController::renderThreadConfig(OperationModeLens lens) -> RenderThread::Config { 84 | auto config = RenderThread::Config{ 85 | .pointerBuffer = m_pointerUpdater.data(), 86 | .outputZoom = lens.outputZoom(), 87 | .captureOffset = lens.captureOffset(), 88 | }; 89 | config.setCallbacks(this); 90 | return config; 91 | } 92 | 93 | auto DuplicationController::captureThreadConfig() -> CaptureThread::Config { 94 | auto config = CaptureThread::Config{}; 95 | config.threadIndex = 0; 96 | config.setCallbacks(this); 97 | return config; 98 | } 99 | 100 | void DuplicationController::startOnMain() { 101 | if (m_status == Status::Paused) { 102 | m_renderWindow.show(); 103 | m_captureThread.next(); 104 | updateStatusOnMain(Status::Resuming); 105 | return; 106 | } 107 | auto canStart = [status = m_status.load()]() { 108 | using enum Status; 109 | switch (status) { 110 | case Stopped: 111 | case Paused: 112 | case Failed: return true; 113 | case Starting: 114 | case Live: 115 | case Stopping: 116 | case Resuming: 117 | case RetryStarting: return false; 118 | } 119 | return false; 120 | }(); 121 | if (!canStart) return; 122 | try { 123 | try { 124 | auto deviceValue = renderer::createDevice(); 125 | auto device = deviceValue.device; 126 | auto deviceContext = deviceValue.deviceContext; 127 | 128 | auto dimensionData = renderer::getDimensionData(device, {m_controller.operatonModeLens().captureMonitor()}); 129 | m_controller.updateScreenRect(dimensionData.rect); 130 | m_displayRect = dimensionData.rect; 131 | 132 | m_targetTexture = renderer::createSharedTexture(device, dimensionData.rect.dimension); 133 | m_windowClass.recreateWindow( 134 | m_renderWindow, createWindowConfig(m_outputWindow, m_controller.config().outputDimension)); 135 | m_renderWindow.setCustomHandler<&DuplicationController::forwardRenderMessage>(this); 136 | m_renderThread.thread().queueUserApc( 137 | [this, 138 | initArgs = WindowRenderer::InitArgs{ 139 | .basic = 140 | { 141 | .device = device, 142 | .deviceContext = deviceContext, 143 | }, 144 | .windowHandle = m_renderWindow.handle(), 145 | .windowDimension = m_controller.config().outputDimension, 146 | .texture = m_targetTexture, 147 | }]() mutable { m_renderThread.windowRenderer().init(std::move(initArgs)); }); 148 | m_renderWindow.show(); 149 | 150 | auto handle = renderer::getSharedHandle(m_targetTexture); 151 | updateStatusOnMain(Status::Starting); 152 | startCaptureThread(handle); 153 | } 154 | catch (const renderer::Error &e) { 155 | throw Expected{e.message}; 156 | } 157 | } 158 | catch (const Expected &e) { 159 | OutputDebugStringA(e.text); 160 | OutputDebugStringA("\n"); 161 | resetOnMain(); 162 | 163 | updateStatusOnMain(Status::RetryStarting); 164 | awaitRetry(); 165 | } 166 | } 167 | 168 | void DuplicationController::pauseOnMain() { 169 | auto canPause = m_status == Status::Live; 170 | if (!canPause) return; 171 | 172 | updateStatusOnMain(Status::Paused); 173 | 174 | if (m_controller.config().operationMode == OperationMode::CaptureArea) { 175 | m_renderWindow.hide(); 176 | } 177 | } 178 | 179 | void DuplicationController::stopOnMain() { 180 | updateStatusOnMain(Status::Stopping); 181 | resetOnMain(); 182 | updateStatusOnMain(Status::Stopped); 183 | } 184 | 185 | void DuplicationController::resetOnMain() { 186 | try { 187 | m_captureThread.stop(); 188 | // m_renderThread.stop(); 189 | m_frameUpdater.reset(); 190 | m_targetTexture.Reset(); 191 | m_renderThread.reset(); 192 | } 193 | catch (Expected &e) { 194 | OutputDebugStringA("resetOnMain Threw: "); 195 | OutputDebugStringA(e.text); 196 | OutputDebugStringA("\n"); 197 | } 198 | } 199 | 200 | void DuplicationController::updateStatusOnMain(Status status) { 201 | if (m_status.load() == status) return; 202 | m_status.store(status); 203 | m_controller.updateSystemStatus([status] { 204 | switch (status) { 205 | case Status::Stopped: return SystemStatus::Neutral; 206 | case Status::Live: return SystemStatus::Green; 207 | case Status::Starting: 208 | case Status::Stopping: 209 | case Status::Resuming: 210 | case Status::Paused: return SystemStatus::Yellow; 211 | case Status::RetryStarting: 212 | case Status::Failed: return SystemStatus::Red; 213 | } 214 | return SystemStatus::Red; // unknown 215 | }()); 216 | } 217 | 218 | void DuplicationController::startCaptureThread(HANDLE targetHandle) { 219 | auto deviceValue = renderer::createDevice(); 220 | auto device = deviceValue.device; 221 | auto deviceContext = deviceValue.deviceContext; 222 | 223 | auto updater_args = FrameUpdater::InitArgs{}; 224 | updater_args.device = device; 225 | updater_args.deviceContext = deviceContext; 226 | updater_args.targetHandle = targetHandle; 227 | m_frameUpdater = FrameUpdater{std::move(updater_args)}; 228 | 229 | auto threadArgs = CaptureThread::StartArgs{}; 230 | threadArgs.display = m_controller.operatonModeLens().captureMonitor(); 231 | threadArgs.device = device; 232 | threadArgs.offset = m_displayRect.topLeft; 233 | m_captureThread.start(std::move(threadArgs)); 234 | } 235 | 236 | void DuplicationController::awaitRetry() { 237 | using namespace std::chrono_literals; 238 | auto timerArgs = WaitableTimer::SetArgs{}; 239 | timerArgs.time = 250ms; 240 | auto success = m_retryTimer.set<&DuplicationController::retryTimeout>(timerArgs, {this}); 241 | if (!success) throw Unexpected{"failed to arm retry timer"}; 242 | } 243 | 244 | void DuplicationController::retryTimeout() { startOnMain(); } 245 | 246 | auto DuplicationController::forwardRenderMessage(const win32::WindowMessage &msg) -> win32::OptLRESULT { 247 | return m_outputWindow.handleMessage(msg); 248 | } 249 | 250 | void DuplicationController::setError(const std::exception_ptr &error) { 251 | m_mainThread.queueUserApc([this, error]() { 252 | try { 253 | std::rethrow_exception(error); 254 | } 255 | catch (const Expected &e) { 256 | OutputDebugStringA(e.text); 257 | OutputDebugStringA("\n"); 258 | stopOnMain(); 259 | updateStatusOnMain(Status::Failed); 260 | awaitRetry(); 261 | } 262 | }); 263 | } 264 | 265 | void DuplicationController::setFrame(CapturedUpdate &&update, const FrameContext &context, size_t threadIndex) { 266 | m_renderThread.thread().queueUserApc([this, update = std::move(update), &context, threadIndex]() mutable { 267 | setFrameOnRender(std::move(update), context, threadIndex); 268 | }); 269 | } 270 | 271 | void DuplicationController::setFrameOnRender( 272 | CapturedUpdate &&update, const FrameContext &context, size_t /*threadIndex*/) { 273 | 274 | // m_frameUpdaters[threadIndex].update(update.frame, context); 275 | m_frameUpdater->update(update.frame, context); 276 | m_pointerUpdater.update(update.pointer, context); 277 | m_renderThread.renderFrame(); 278 | 279 | auto status = m_status.load(); 280 | if (status == Status::Live) { 281 | m_captureThread.next(); 282 | } 283 | else if (status == Status::Starting || status == Status::Resuming) { 284 | m_mainThread.queueUserApc([this]() { 285 | auto current = m_status.load(); 286 | if (current == Status::Starting || current == Status::Resuming) { 287 | updateStatusOnMain(Status::Live); 288 | m_captureThread.next(); 289 | } 290 | }); 291 | } 292 | } 293 | 294 | } // namespace deskdup 295 | -------------------------------------------------------------------------------- /src/win32/Window.cpp: -------------------------------------------------------------------------------- 1 | #include "Window.h" 2 | 3 | namespace win32 { 4 | 5 | namespace { 6 | 7 | auto extractWindow(HWND windowHandle, UINT message, LPARAM lParam) -> WindowWithMessages * { 8 | switch (message) { 9 | case WM_NCCREATE: { 10 | auto create_struct = std::bit_cast(lParam); 11 | auto *window = static_cast(create_struct->lpCreateParams); 12 | ::SetWindowLongPtrW(windowHandle, GWLP_USERDATA, std::bit_cast(window)); 13 | // windowClass->handleWindowSetup(window); 14 | return window; 15 | } 16 | default: return std::bit_cast(::GetWindowLongPtr(windowHandle, GWLP_USERDATA)); 17 | } 18 | } 19 | 20 | auto ensureInstance(WindowClass::Config &config) { 21 | if (!config.instanceHandle) { 22 | config.instanceHandle = ::GetModuleHandleW(nullptr); 23 | } 24 | return config.instanceHandle; 25 | } 26 | 27 | auto createWindow(void *window, const WindowWithMessages::Config &config, HINSTANCE instance, const Name &className) 28 | -> HWND { 29 | return ::CreateWindowExW( 30 | config.style.exStyle, 31 | className.data(), 32 | config.name.data(), 33 | config.style.style, 34 | config.rect.topLeft.x, 35 | config.rect.topLeft.y, 36 | config.rect.dimension.width, 37 | config.rect.dimension.height, 38 | config.parent ? config.parent->handle() : HWND{}, 39 | config.menu.hMenu, 40 | instance, 41 | window); 42 | } 43 | 44 | } // namespace 45 | 46 | // 47 | // Window 48 | // 49 | 50 | auto Window::rect() const -> Rect { 51 | auto rect = RECT{}; 52 | ::GetWindowRect(m_windowHandle, &rect); 53 | return Rect::fromRECT(rect); 54 | } 55 | 56 | auto Window::clientRect() const -> Rect { 57 | auto rect = RECT{}; 58 | ::GetClientRect(m_windowHandle, &rect); 59 | return Rect::fromRECT(rect); 60 | } 61 | 62 | auto Window::clientOffset() const -> Point { 63 | auto windowInfo = WINDOWINFO{}; 64 | windowInfo.cbSize = sizeof(WINDOWINFO); 65 | ::GetWindowInfo(m_windowHandle, &windowInfo); 66 | return Point{ 67 | .x = windowInfo.rcWindow.left - windowInfo.rcClient.left, 68 | .y = windowInfo.rcWindow.top - windowInfo.rcClient.top, 69 | }; 70 | } 71 | 72 | auto Window::placement() const -> Placement { 73 | auto windowPlacement = WINDOWPLACEMENT{}; 74 | windowPlacement.length = sizeof(WINDOWPLACEMENT); 75 | ::GetWindowPlacement(m_windowHandle, &windowPlacement); 76 | auto currentRect = rect(); 77 | return Placement{ 78 | [&]() -> ShowState { 79 | if (windowPlacement.showCmd == SW_SHOWMAXIMIZED) return ShowState::Maximized; 80 | if (windowPlacement.showCmd == SW_SHOWMINIMIZED) return ShowState::Minimized; 81 | return ShowState::Normal; 82 | }(), 83 | currentRect, // use correct values! 84 | Rect::fromRECT(windowPlacement.rcNormalPosition), 85 | }; 86 | } 87 | 88 | bool Window::isMaximized() const { return ::IsZoomed(m_windowHandle); } 89 | bool Window::isMinimized() const { return ::IsIconic(m_windowHandle); } 90 | 91 | auto Window::style() const -> WindowStyle { 92 | auto style = (DWORD)::GetWindowLongPtr(m_windowHandle, GWL_STYLE); 93 | auto exStyle = (DWORD)::GetWindowLongPtr(m_windowHandle, GWL_EXSTYLE); 94 | return WindowStyle{style, exStyle}; 95 | } 96 | 97 | bool Window::isVisible() const { return ::IsWindowVisible(m_windowHandle); } 98 | 99 | auto Window::dpiAwareness() const -> DpiAwareness { 100 | auto context = ::GetWindowDpiAwarenessContext(m_windowHandle); 101 | if (context == DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) return DpiAwareness::PerMonitorAwareV2; 102 | if (context == DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE) return DpiAwareness::PerMonitorAware; 103 | if (context == DPI_AWARENESS_CONTEXT_SYSTEM_AWARE) return DpiAwareness::SystemAware; 104 | if (context == DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED) return DpiAwareness::UnawareGdiScaled; 105 | if (context == DPI_AWARENESS_CONTEXT_UNAWARE) return DpiAwareness::Unaware; 106 | return DpiAwareness::Unknown; 107 | } 108 | 109 | auto Window::dpi() const -> Dpi { return Dpi{::GetDpiForWindow(m_windowHandle)}; } 110 | 111 | auto Window::text() const -> String { 112 | auto result = String{}; 113 | auto len = ::GetWindowTextLengthW(m_windowHandle); 114 | if (len) { 115 | auto maxSize = len + 2; 116 | result.resize(maxSize); 117 | auto actual = ::GetWindowTextW(m_windowHandle, result.data(), maxSize); 118 | result.resize(actual); 119 | result.shrink_to_fit(); 120 | } 121 | return result; 122 | } 123 | 124 | // note: not working on Windows10 (only sees own module) 125 | // auto Window::moduleFileName() const -> String { 126 | // auto result = String{}; 127 | // result.resize(65535); 128 | // auto actual = ::GetWindowModuleFileNameW(m_windowHandle, result.data(), result.size()); 129 | // result.resize(actual); 130 | // result.shrink_to_fit(); 131 | // return result; 132 | // } 133 | 134 | void Window::show() { ::ShowWindowAsync(m_windowHandle, SW_SHOW); } 135 | void Window::hide() { ::ShowWindowAsync(m_windowHandle, SW_HIDE); } 136 | 137 | void Window::toBottom() { 138 | auto flags = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE; 139 | ::SetWindowPos(m_windowHandle, HWND_BOTTOM, 0, 0, 0, 0, flags); 140 | } 141 | 142 | void Window::toTop() { 143 | auto flags = SWP_NOMOVE | SWP_NOSIZE; 144 | ::SetWindowPos(m_windowHandle, ::GetTopWindow(nullptr), 0, 0, 0, 0, flags); 145 | ::SetForegroundWindow(m_windowHandle); 146 | } 147 | 148 | void Window::showNormal() { ::ShowWindowAsync(m_windowHandle, SW_SHOWNORMAL); } 149 | void Window::showMaximized() { ::ShowWindowAsync(m_windowHandle, SW_SHOWMAXIMIZED); } 150 | void Window::showMinimized() { ::ShowWindowAsync(m_windowHandle, SW_SHOWMINIMIZED); } 151 | 152 | void Window::update() { ::UpdateWindow(m_windowHandle); } 153 | 154 | void Window::move(Rect rect) { 155 | const auto repaint = true; 156 | ::MoveWindow(m_windowHandle, rect.left(), rect.top(), rect.width(), rect.height(), repaint); 157 | } 158 | void Window::moveBorder(Rect rect) { 159 | auto wRect = rect.toRECT(); 160 | auto hasMenu = false; 161 | ::AdjustWindowRectEx(&wRect, WS_THICKFRAME, hasMenu, WS_EX_OVERLAPPEDWINDOW); 162 | auto aRect = Rect::fromRECT(wRect); 163 | auto top = rect.top(); 164 | auto height = aRect.height() - (top - aRect.top()); 165 | const auto repaint = true; 166 | ::MoveWindow(m_windowHandle, aRect.left(), top, aRect.width(), height, repaint); 167 | } 168 | 169 | void Window::moveClient(Rect rect) { 170 | auto windowInfo = WINDOWINFO{}; 171 | windowInfo.cbSize = sizeof(WINDOWINFO); 172 | ::GetWindowInfo(m_windowHandle, &windowInfo); 173 | auto top = rect.top() - windowInfo.rcClient.top + windowInfo.rcWindow.top; 174 | auto left = rect.left() - windowInfo.rcClient.left + windowInfo.rcWindow.left; 175 | auto right = rect.right() - windowInfo.rcClient.right + windowInfo.rcWindow.right; 176 | auto bottom = rect.bottom() - windowInfo.rcClient.bottom + windowInfo.rcWindow.bottom; 177 | const auto repaint = true; 178 | ::MoveWindow(m_windowHandle, left, top, right - left, bottom - top, repaint); 179 | } 180 | 181 | void Window::setPosition(Point point) { 182 | auto flags = SWP_NOZORDER | SWP_NOSIZE | SWP_NOSENDCHANGING | SWP_ASYNCWINDOWPOS; 183 | ::SetWindowPos(m_windowHandle, nullptr, point.x, point.y, 0, 0, flags); 184 | } 185 | 186 | bool Window::setPlacement(const Placement &placement) { 187 | auto const windowPlacement = WINDOWPLACEMENT{ 188 | .length = sizeof(WINDOWPLACEMENT), 189 | .flags = WPF_ASYNCWINDOWPLACEMENT, 190 | .showCmd = [&]() -> UINT { 191 | switch (placement.showState) { 192 | case ShowState::Normal: return SW_SHOWNORMAL; 193 | case ShowState::Maximized: return SW_SHOWMAXIMIZED; 194 | case ShowState::Minimized: return SW_SHOWMINIMIZED; 195 | } 196 | return SW_HIDE; 197 | }(), 198 | .ptMinPosition = placement.currentRect.topLeft.toPOINT(), 199 | .ptMaxPosition = placement.currentRect.bottomRight().toPOINT(), 200 | .rcNormalPosition = placement.normalRect.toRECT(), 201 | }; 202 | return 0 != ::SetWindowPlacement(m_windowHandle, &windowPlacement); 203 | } 204 | 205 | void Window::styleNonLayered() { 206 | auto exStyle = GetWindowLong(m_windowHandle, GWL_EXSTYLE) & ~WS_EX_LAYERED; 207 | SetWindowLong(m_windowHandle, GWL_EXSTYLE, exStyle); 208 | } 209 | 210 | void Window::styleLayeredColorKey(uint32_t color) { 211 | auto exStyle = GetWindowLong(m_windowHandle, GWL_EXSTYLE) | WS_EX_LAYERED; 212 | SetWindowLong(m_windowHandle, GWL_EXSTYLE, exStyle); 213 | const auto alpha = BYTE{0u}; 214 | SetLayeredWindowAttributes(m_windowHandle, color, alpha, LWA_COLORKEY); 215 | } 216 | 217 | void Window::styleLayeredAlpha(uint8_t alpha) { 218 | auto exStyle = GetWindowLong(m_windowHandle, GWL_EXSTYLE) | WS_EX_LAYERED; 219 | SetWindowLong(m_windowHandle, GWL_EXSTYLE, exStyle); 220 | const auto color = DWORD{0u}; 221 | SetLayeredWindowAttributes(m_windowHandle, color, alpha, LWA_ALPHA); 222 | } 223 | 224 | // 225 | // WindowWithMessages 226 | // 227 | 228 | WindowWithMessages::WindowWithMessages( 229 | const WindowWithMessages::Config &config, HINSTANCE instance, const Name &className) { 230 | m_windowHandle = createWindow(this, config, instance, className); 231 | } 232 | 233 | WindowWithMessages::~WindowWithMessages() noexcept { ::DestroyWindow(m_windowHandle); } 234 | 235 | void WindowWithMessages::resetMessageHandler() { 236 | m_messageHandlerFunc = &WindowWithMessages::noopMessageHandler; 237 | m_messageHandlerPtr = nullptr; 238 | } 239 | 240 | auto WindowWithMessages::noopMessageHandler(void *, const WindowMessage &) -> OptLRESULT { return {}; } 241 | 242 | auto WindowWithMessages::handleMessage(const WindowMessage &msg) -> OptLRESULT { 243 | return m_messageHandlerFunc(m_messageHandlerPtr, msg); 244 | } 245 | 246 | // 247 | // WindowClass 248 | // 249 | 250 | WindowClass::WindowClass(Config config) 251 | : m_instanceHandle(ensureInstance(config)) 252 | , m_className(config.className) 253 | , m_atom(registerWindowClass(config)) {} 254 | 255 | WindowClass::~WindowClass() noexcept { ::UnregisterClassW(m_className.data(), m_instanceHandle); } 256 | 257 | auto WindowClass::createWindow(WindowWithMessages::Config config) const -> WindowWithMessages { 258 | return WindowWithMessages{config, m_instanceHandle, m_className}; 259 | } 260 | 261 | void WindowClass::recreateWindow(WindowWithMessages &wnd, Window::Config config) const { 262 | wnd.~WindowWithMessages(); 263 | new (&wnd) WindowWithMessages{config, m_instanceHandle, m_className}; 264 | } 265 | 266 | auto WindowClass::registerWindowClass(const Config &config) -> ATOM { 267 | auto windowClass = WNDCLASSEXW{}; 268 | windowClass.cbSize = sizeof(WNDCLASSEXW); 269 | windowClass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 270 | windowClass.lpfnWndProc = WindowClass::staticWindowProc; 271 | windowClass.cbClsExtra = 0; 272 | windowClass.cbWndExtra = 0; 273 | windowClass.hInstance = config.instanceHandle; 274 | windowClass.hIcon = config.icon.hIcon; 275 | windowClass.hCursor = config.cursor.hCursor; 276 | windowClass.hbrBackground = nullptr; 277 | windowClass.lpszMenuName = config.menuName.data(); // TODO: ensure zero! 278 | windowClass.lpszClassName = config.className.data(); 279 | windowClass.hIconSm = config.smallIcon.hIcon; 280 | 281 | return RegisterClassExW(&windowClass); 282 | } 283 | 284 | auto CALLBACK WindowClass::staticWindowProc(HWND windowHandle, UINT message, WPARAM wParam, LPARAM lParam) -> LRESULT { 285 | 286 | auto *window = extractWindow(windowHandle, message, lParam); 287 | if (window) { 288 | auto result = window->handleMessage(WindowMessage{windowHandle, message, wParam, lParam}); 289 | if (result) return *result; 290 | } 291 | return ::DefWindowProc(windowHandle, message, wParam, lParam); 292 | } 293 | 294 | } // namespace win32 295 | -------------------------------------------------------------------------------- /src/win32/WindowMessageHandler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Geometry.h" 3 | 4 | #include 5 | #include // GET_X_LPARAM 6 | 7 | #include 8 | #include 9 | #include // uint32_t 10 | 11 | namespace win32 { 12 | 13 | using OptLRESULT = std::optional; 14 | 15 | using Milliseconds = std::chrono::milliseconds; 16 | 17 | struct WindowMessage { 18 | HWND window = {}; 19 | uint32_t type = {}; 20 | WPARAM wParam = {}; 21 | LPARAM lParam = {}; 22 | }; 23 | struct ControlCommand { 24 | WORD notificationCode{}; 25 | WORD identifier{}; 26 | LPARAM handle{}; 27 | }; 28 | struct AppCommand { 29 | WORD device; // FAPPCOMMAND_KEY | FAPPCOMMAND_MOUSE | FAPPCOMMAND_OEM 30 | WORD keys; // MK_CONTROL | MK_LBUTTON | MK_MBUTTON | MK_RBUTTON | MK_SHIFT | MK_XBUTTON1 | 31 | // MK_XBUTTON2 32 | }; 33 | struct WindowPosition { 34 | HWND hwnd; 35 | HWND insertAfter; 36 | Point topLeft; 37 | Dimension dimension; 38 | UINT flags; 39 | 40 | constexpr bool hasZOrder() const { return 0 == (flags & SWP_NOZORDER); } 41 | constexpr bool hasTopLeft() const { return 0 == (flags & SWP_NOREPOSITION); } 42 | constexpr bool hasDimension() const { return 0 == (flags & SWP_NOSIZE); } 43 | 44 | constexpr static auto fromWINDOWPOS(WINDOWPOS const &winPos) -> WindowPosition { 45 | return { 46 | .hwnd = winPos.hwnd, 47 | .insertAfter = winPos.hwndInsertAfter, 48 | .topLeft = Point{winPos.x, winPos.y}, 49 | .dimension = Dimension{winPos.cx, winPos.cy}, 50 | .flags = winPos.flags, 51 | }; 52 | } 53 | }; 54 | 55 | /// CRTP Wrapper to easily handle messages 56 | /// usage: 57 | /// struct MyHandler final : private MessageHandler { 58 | /// private: 59 | /// friend struct MessageHandler; 60 | /// // override only the handlers you need! 61 | /// void close() override; 62 | /// }; 63 | /// note: 64 | /// * we use virtual to allow using the override keyword 65 | /// * all message methods are called directly without virtual dispatch! 66 | template 67 | struct WindowMessageHandler { 68 | #pragma warning(push) 69 | #pragma warning(disable : 4100) // unereference parameter - its here for documentation only! 70 | virtual void create(CREATESTRUCT *) {} 71 | virtual void destroy() {} 72 | virtual void size(const Dimension &, uint32_t) {} 73 | virtual void move(const Point &topLeft) {} 74 | virtual bool position(const WindowPosition &) { 75 | return false; // note: move & size is not called if true is returned! 76 | } 77 | // virtual void activate() {} 78 | virtual void setFocus() {} 79 | virtual void killFocus() {} 80 | // virtual void enable() {} 81 | // virtual void setRedraw() {} 82 | // virtual void setText() {} 83 | // virtual void getText() {} 84 | // virtual void getTextLength() {} 85 | virtual bool paint() { return false; } //< return true only if painted! 86 | virtual void close() {} 87 | // virtual void queryEndSession() {} 88 | // virtual void queryOpen() {} 89 | // virtual void endSession() {} 90 | // virtual void quit() {} 91 | // virtual void eraseBackground() {} 92 | // virtual void systemColorChange() {} 93 | // virtual void showWindow() {} 94 | // virtual void windowsIniChange() {} 95 | // … 96 | virtual void displayChange(uint32_t bitsPerPixel, Dimension) {} 97 | 98 | // Input handling 99 | virtual bool inputKeyDown(uint32_t keyCode) { return false; } 100 | virtual bool inputKeyUp(uint32_t keyCode) { return false; } 101 | virtual void inputChar() {} 102 | // virtual void inputDeadChar() {} 103 | virtual void inputUnicodeChar() {} 104 | // … 105 | 106 | // Mouse handling 107 | virtual void mouseMove(const Point &mousePosition, DWORD keyState) {} 108 | virtual void mouseLeftButtonDown(const Point &mousePosition, DWORD keyState) {} 109 | virtual void mouseLeftButtonUp(const Point &mousePosition, DWORD keyState) {} 110 | virtual void mouseLeftButtonDoubleClick(const Point &mousePosition, DWORD keyState) {} 111 | virtual void mouseRightButtonDown(const Point &mousePosition, DWORD keyState) {} 112 | virtual bool mouseRightButtonUp(const Point &mousePosition, DWORD keyState) { return false; } 113 | virtual void mouseRightDoubleClick(const Point &mousePosition, DWORD keyState) {} 114 | virtual void mouseMiddleButtonDown(const Point &mousePosition, DWORD keyState) {} 115 | virtual void mouseMiddleButtonUp(const Point &mousePosition, DWORD keyState) {} 116 | virtual void mouseMiddleButtonDoubleClick(const Point &mousePosition, DWORD keyState) {} 117 | virtual void mouseWheel(int wheelDelta, const Point &mousePosition, DWORD keyState) {} 118 | virtual void mouseExtraButtonDown() {} 119 | virtual void mouseExtraButtonUp() {} 120 | virtual void mouseExtraButtonDoubleClick() {} 121 | virtual void contextMenu(const Point &position) {} 122 | // … 123 | 124 | virtual void dpiChanged(const Rect &) {} 125 | 126 | virtual auto deviceChange(WPARAM event, LPARAM pointer) -> LRESULT { return {}; } 127 | 128 | virtual auto sysCommand(uint16_t command, const Point &mousePosition) -> OptLRESULT { return {}; } 129 | virtual auto appCommand(uint32_t command, const AppCommand &) -> OptLRESULT { return {}; } 130 | virtual auto menuCommand(uint16_t command) -> OptLRESULT { return {}; } 131 | virtual auto acceleratorCommand(uint16_t command) -> OptLRESULT { return {}; } 132 | virtual auto controlCommand(const ControlCommand &) -> OptLRESULT { return {}; } 133 | 134 | virtual void timer(HWND hwnd, WPARAM timer, TIMERPROC timerProc) { 135 | #pragma warning(suppress : 28159) // API requires GetTickCount! 136 | if (timerProc) timerProc(hwnd, WM_TIMER, timer, GetTickCount()); 137 | } 138 | 139 | // user defined messages 140 | virtual auto userMessage(const WindowMessage &) -> OptLRESULT { return {}; } 141 | 142 | virtual void powerStatusChange() {} 143 | virtual void powerResumeAutomatic() {} 144 | virtual void powerResumeSuspend() {} 145 | virtual void powerSuspend() {} 146 | virtual void powerSettingChange(POWERBROADCAST_SETTING *) {} 147 | #pragma warning(pop) 148 | protected: 149 | /// mouse position at the occurence of the processed message 150 | static auto messageMousePosition() -> Point { 151 | auto pos = ::GetMessagePos(); 152 | return {GET_X_LPARAM(pos), GET_Y_LPARAM(pos)}; 153 | } 154 | 155 | /// time the message was send 156 | static auto messageTime() -> Milliseconds { return Milliseconds{::GetMessageTime()}; } 157 | 158 | /// construct modifier keys for messages that do not get them 159 | static auto modifierKeys() noexcept -> uint32_t { 160 | auto output = 0u; 161 | if ((::GetKeyState(VK_SHIFT) & 0x8000) != 0) output |= MK_SHIFT; 162 | if ((::GetKeyState(VK_CONTROL) & 0x8000) != 0) output |= MK_CONTROL; 163 | if ((::GetKeyState(VK_MENU) & 0x8000) != 0) output |= MK_ALT; 164 | return output; 165 | } 166 | 167 | private: 168 | friend struct WindowWithMessages; 169 | 170 | static auto handleMessage(void *actual, const WindowMessage &msg) -> OptLRESULT { 171 | static constexpr auto fromBool = [](bool b) -> OptLRESULT { return b ? OptLRESULT{LRESULT{}} : OptLRESULT{}; }; 172 | auto m = std::bit_cast(actual); 173 | auto &[window, message, wParam, lParam] = msg; 174 | switch (message) { 175 | case WM_CREATE: return m->create(std::bit_cast(lParam)), LRESULT{}; 176 | case WM_DESTROY: return m->destroy(), LRESULT{}; 177 | case WM_SIZE: 178 | return m->size(Dimension{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}, static_cast(wParam)), 179 | LRESULT{}; 180 | case WM_MOVE: return m->move(Point{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}), LRESULT{}; 181 | case WM_WINDOWPOSCHANGED: 182 | return fromBool(m->position(WindowPosition::fromWINDOWPOS(*std::bit_cast(lParam)))); 183 | case WM_SETFOCUS: return m->setFocus(), LRESULT{}; 184 | case WM_KILLFOCUS: return m->killFocus(), LRESULT{}; 185 | 186 | case WM_PAINT: return fromBool(m->paint()); 187 | case WM_CLOSE: return m->close(), LRESULT{}; 188 | 189 | case WM_KEYDOWN: return fromBool(m->inputKeyDown(static_cast(wParam))); 190 | case WM_KEYUP: return fromBool(m->inputKeyUp(static_cast(wParam))); 191 | 192 | case WM_MOUSEMOVE: 193 | return m->mouseMove(Point{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}, GET_KEYSTATE_WPARAM(wParam)), 194 | LRESULT{}; 195 | case WM_LBUTTONDOWN: 196 | return m->mouseLeftButtonDown( 197 | Point{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}, GET_KEYSTATE_WPARAM(wParam)), 198 | LRESULT{}; 199 | case WM_LBUTTONUP: 200 | return m->mouseLeftButtonUp(Point{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}, GET_KEYSTATE_WPARAM(wParam)), 201 | LRESULT{}; 202 | case WM_LBUTTONDBLCLK: 203 | return m->mouseLeftButtonDoubleClick( 204 | Point{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}, GET_KEYSTATE_WPARAM(wParam)), 205 | LRESULT{}; 206 | case WM_RBUTTONDOWN: 207 | return m->mouseRightButtonDown( 208 | Point{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}, GET_KEYSTATE_WPARAM(wParam)), 209 | LRESULT{}; 210 | case WM_RBUTTONUP: 211 | return fromBool( 212 | m->mouseRightButtonUp(Point{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}, GET_KEYSTATE_WPARAM(wParam))); 213 | case WM_RBUTTONDBLCLK: 214 | return m->mouseRightDoubleClick( 215 | Point{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}, GET_KEYSTATE_WPARAM(wParam)), 216 | LRESULT{}; 217 | case WM_MOUSEWHEEL: 218 | return m->mouseWheel( 219 | GET_WHEEL_DELTA_WPARAM(wParam), 220 | Point{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}, 221 | GET_KEYSTATE_WPARAM(wParam)), 222 | LRESULT{}; 223 | case WM_CONTEXTMENU: return m->contextMenu(Point{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}), LRESULT{}; 224 | 225 | case WM_DISPLAYCHANGE: 226 | return m->displayChange( 227 | static_cast(wParam), Dimension{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}), 228 | LRESULT{}; 229 | 230 | case WM_COMMAND: return handleCommand(actual, msg); 231 | case WM_APPCOMMAND: 232 | return m->appCommand( 233 | GET_APPCOMMAND_LPARAM(lParam), {GET_DEVICE_LPARAM(lParam), GET_KEYSTATE_LPARAM(lParam)}); 234 | case WM_SYSCOMMAND: return m->sysCommand(wParam & 0xFFF0, Point{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}); 235 | 236 | case WM_TIMER: return m->timer(window, wParam, std::bit_cast(lParam)), LRESULT{}; 237 | 238 | case WM_DPICHANGED: return m->dpiChanged(Rect::fromRECT(*std::bit_cast(lParam))), LRESULT{}; 239 | 240 | case WM_DEVICECHANGE: return m->deviceChange(wParam, lParam); 241 | 242 | case WM_POWERBROADCAST: return handlePowerBroadcast(actual, msg); 243 | 244 | case WM_NCHITTEST: { 245 | auto result = ::DefWindowProc(window, message, wParam, lParam); 246 | return result; 247 | } 248 | 249 | default: 250 | if (message >= WM_USER) return m->Actual::userMessage(msg); 251 | } 252 | return {}; // unhandled 253 | } 254 | static auto handleCommand(void *actual, const WindowMessage &msg) -> OptLRESULT { 255 | auto m = std::bit_cast(actual); 256 | auto &[window, message, wParam, lParam] = msg; 257 | switch (HIWORD(wParam)) { 258 | case 0: return m->menuCommand(LOWORD(wParam)); 259 | case 1: return m->acceleratorCommand(LOWORD(wParam)); 260 | default: return m->controlCommand({HIWORD(wParam), LOWORD(wParam), lParam}); 261 | } 262 | } 263 | static auto handlePowerBroadcast(void *actual, const WindowMessage &msg) -> OptLRESULT { 264 | auto m = std::bit_cast(actual); 265 | auto &[window, message, wParam, lParam] = msg; 266 | switch (wParam) { 267 | case PBT_APMPOWERSTATUSCHANGE: return m->powerStatusChange(), LRESULT{}; 268 | case PBT_APMRESUMEAUTOMATIC: return m->powerResumeAutomatic(), LRESULT{}; 269 | case PBT_APMRESUMESUSPEND: return m->powerResumeSuspend(), LRESULT{}; 270 | case PBT_APMSUSPEND: return m->powerSuspend(), LRESULT{}; 271 | case PBT_POWERSETTINGCHANGE: 272 | return m->powerSettingChange(std::bit_cast(lParam)), LRESULT{}; 273 | } 274 | return {}; 275 | } 276 | }; 277 | 278 | } // namespace win32 279 | -------------------------------------------------------------------------------- /src/WindowRenderer.cpp: -------------------------------------------------------------------------------- 1 | #include "WindowRenderer.h" 2 | #include "PointerUpdater.h" 3 | 4 | #include "renderer.h" 5 | 6 | #include "MaskedPixelShader.h" 7 | 8 | #include 9 | 10 | using Error = renderer::Error; 11 | using win32::Rect; 12 | 13 | WindowRenderer::WindowRenderer(const Args &config) 14 | : m_args{config} {} 15 | 16 | void WindowRenderer::init(InitArgs &&args) { 17 | m_dx.emplace(std::move(args)); 18 | m_size = args.windowDimension; 19 | } 20 | 21 | void WindowRenderer::reset() noexcept { m_dx.reset(); } 22 | 23 | auto WindowRenderer::frameLatencyWaitable() -> Handle { 24 | return Handle{m_dx->swapChain->GetFrameLatencyWaitableObject()}; 25 | } 26 | 27 | bool WindowRenderer::resize(Dimension size) noexcept { 28 | if (size == m_size) return false; 29 | m_size = size; 30 | m_pendingResizeBuffers = true; 31 | return true; 32 | } 33 | 34 | void WindowRenderer::zoomOutput(float zoom) noexcept { m_args.outputZoom = zoom; } 35 | 36 | void WindowRenderer::updateOffset(Vec2f offset) noexcept { m_args.captureOffset = offset; } 37 | 38 | void WindowRenderer::render() { 39 | try { 40 | renderBlack(); 41 | renderFrame(); 42 | renderPointer(); 43 | swap(); 44 | } 45 | catch (...) { 46 | m_args.setErrorCallback(m_args.callbackPtr, std::current_exception()); 47 | } 48 | } 49 | 50 | void WindowRenderer::renderBlack() { 51 | auto &dx = *m_dx; 52 | if (m_pendingResizeBuffers) { 53 | m_pendingResizeBuffers = false; 54 | dx.renderTarget.Reset(); 55 | resizeSwapBuffer(); 56 | dx.createRenderTarget(); 57 | } 58 | 59 | auto black = BaseRenderer::Color{0, 0, 0, 0}; 60 | dx.clearRenderTarget(black); 61 | } 62 | 63 | void WindowRenderer::renderFrame() { 64 | auto &dx = *m_dx; 65 | // dx.deviceContext()->GenerateMips(dx.backgroundTextureShaderResource.Get()); 66 | 67 | setViewPort(); 68 | dx.activateRenderTarget(); 69 | 70 | dx.activateLinearSampler(); 71 | dx.activateVertexShader(); 72 | dx.activateDiscreteSampler(); 73 | dx.activatePlainPixelShader(); 74 | dx.activateBackgroundTexture(); 75 | dx.activateNoBlendState(); 76 | dx.activateTriangleList(); 77 | dx.activateBackgroundVertexBuffer(); 78 | dx.deviceContext()->Draw(6, 0); 79 | } 80 | 81 | void WindowRenderer::renderPointer() { 82 | auto &pointer = m_args.pointerBuffer; 83 | if (pointer.position_timestamp == 0) return; 84 | updatePointerShape(pointer); 85 | if (!pointer.visible) return; 86 | updatePointerVertices(pointer); 87 | 88 | auto &dx = *m_dx; 89 | dx.activatePointerVertexBuffer(); 90 | 91 | if (pointer.shape_info.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR) { 92 | dx.activateAlphaBlendState(); 93 | dx.activatePlainPixelShader(); 94 | dx.activatePointerTexture(); 95 | } 96 | else { 97 | auto const index = 1; 98 | // dx.activateNoBlendState(); -- already set 99 | dx.activatePointerTexture(index); 100 | dx.activateDiscreteSampler(index); 101 | dx.activateMaskedPixelShader(); 102 | } 103 | 104 | dx.deviceContext()->Draw(6, 0); 105 | } 106 | 107 | void WindowRenderer::swap() { 108 | auto const &dx = *m_dx; 109 | dx.activateNoRenderTarget(); 110 | 111 | auto const result = dx.swapChain->Present(1, 0); 112 | if (IS_ERROR(result)) throw Error{result, "Failed to swap buffers"}; 113 | } 114 | 115 | void WindowRenderer::resizeSwapBuffer() { 116 | auto const &dx = *m_dx; 117 | auto description = DXGI_SWAP_CHAIN_DESC{}; 118 | dx.swapChain->GetDesc(&description); 119 | auto const result = dx.swapChain->ResizeBuffers( 120 | description.BufferCount, m_size.width, m_size.height, description.BufferDesc.Format, description.Flags); 121 | if (IS_ERROR(result)) throw Error{result, "Failed to resize swap buffers"}; 122 | } 123 | 124 | void WindowRenderer::setViewPort() { 125 | auto const &dx = *m_dx; 126 | auto texture_description = D3D11_TEXTURE2D_DESC{}; 127 | dx.backgroundTexture->GetDesc(&texture_description); 128 | 129 | auto zoom = m_args.outputZoom; 130 | auto offset = m_args.captureOffset; 131 | auto view_port = D3D11_VIEWPORT{ 132 | .TopLeftX = static_cast(offset.x) * zoom, 133 | .TopLeftY = static_cast(offset.y) * zoom, 134 | .Width = static_cast(texture_description.Width) * zoom, 135 | .Height = static_cast(texture_description.Height) * zoom, 136 | .MinDepth = 0.0f, 137 | .MaxDepth = 1.0f, 138 | }; 139 | dx.deviceContext()->RSSetViewports(1, &view_port); 140 | } 141 | 142 | void WindowRenderer::updatePointerShape(const PointerBuffer &pointer) { 143 | if (pointer.shape_timestamp == m_lastPointerShapeUpdate) return; 144 | m_lastPointerShapeUpdate = pointer.shape_timestamp; 145 | 146 | auto texture_description = D3D11_TEXTURE2D_DESC{ 147 | .Width = pointer.shape_info.Width, 148 | .Height = pointer.shape_info.Height, 149 | .MipLevels = 1, 150 | .ArraySize = 1, 151 | .Format = DXGI_FORMAT_B8G8R8A8_UNORM, 152 | .SampleDesc = 153 | DXGI_SAMPLE_DESC{ 154 | .Count = 1, 155 | .Quality = 0, 156 | }, 157 | .Usage = D3D11_USAGE_DEFAULT, 158 | .BindFlags = D3D11_BIND_SHADER_RESOURCE, 159 | .CPUAccessFlags = 0, 160 | .MiscFlags = 0, 161 | }; 162 | auto resource_data = D3D11_SUBRESOURCE_DATA{ 163 | .pSysMem = pointer.shape_data.data(), 164 | .SysMemPitch = pointer.shape_info.Pitch, 165 | .SysMemSlicePitch = 0, 166 | }; 167 | 168 | // convert monochrome to masked colors 169 | using Color = std::array; 170 | auto tmpData = std::vector{}; 171 | if (pointer.shape_info.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME) { 172 | auto const width = texture_description.Width; 173 | auto const height = texture_description.Height >> 1; 174 | auto const pitch = pointer.shape_info.Pitch; 175 | auto const xor_offset = static_cast(height) * pitch; 176 | tmpData.resize(static_cast(width) * height); 177 | for (auto row = size_t{}; row < height; ++row) { 178 | for (auto col = 0u; col < width; ++col) { 179 | auto const mask = 0x80 >> (col & 7); 180 | auto const addr = (col >> 3) + (row * pitch); 181 | auto const and_mask = pointer.shape_data[addr] & mask; 182 | auto const xor_mask = pointer.shape_data[addr + xor_offset] & mask; 183 | auto const pixel = and_mask 184 | ? (xor_mask ? Color{{0xFF, 0xFF, 0xFF, 0xFF}} : Color{{0x00, 0x00, 0x00, 0xFF}}) 185 | : (xor_mask ? Color{{0xFF, 0xFF, 0xFF, 0x00}} : Color{{0x00, 0x00, 0x00, 0x00}}); 186 | tmpData[row * width + col] = pixel; 187 | } 188 | } 189 | 190 | texture_description.Height = height; 191 | resource_data.pSysMem = tmpData.data(); 192 | resource_data.SysMemPitch = width * sizeof(Color); 193 | } 194 | //{ 195 | // auto width = texture_description.Width; 196 | // auto height = texture_description.Height; 197 | // auto pitch = resource_data.SysMemPitch; 198 | // const uint8_t* data = std::bit_cast(resource_data.pSysMem); 199 | // for (auto row = 0u; row < height; ++row) { 200 | // for (auto col = 0u; col < width; ++col) { 201 | // auto addr = (col * 4) + (row * pitch); 202 | // auto Color = reinterpret_cast(data[addr]); 203 | // char ch[2] = {'u', '\0'}; 204 | // switch (Color) { 205 | // case 0x00000000: ch[0] = 'b'; break; 206 | // case 0x00FFFFFF: ch[0] = 'w'; break; 207 | // case 0xFF000000: ch[0] = ' '; break; 208 | // case 0xFFFFFFFF: ch[0] = 'x'; break; 209 | // default: 210 | // if (Color & 0xFF000000) ch[0] = 'n'; 211 | // } 212 | // OutputDebugStringA(ch); 213 | // } 214 | // OutputDebugStringA("\n"); 215 | // } 216 | //} 217 | 218 | auto &dx = *m_dx; 219 | auto result = dx.device()->CreateTexture2D(&texture_description, &resource_data, &dx.pointerTexture); 220 | if (IS_ERROR(result)) throw Error{result, "Failed to create texture 2d"}; 221 | 222 | auto shader_resource_description = D3D11_SHADER_RESOURCE_VIEW_DESC{ 223 | .Format = texture_description.Format, 224 | .ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D, 225 | .Texture2D = 226 | D3D11_TEX2D_SRV{ 227 | .MostDetailedMip = texture_description.MipLevels - 1, 228 | .MipLevels = texture_description.MipLevels, 229 | }, 230 | }; 231 | result = dx.device()->CreateShaderResourceView( 232 | dx.pointerTexture.Get(), &shader_resource_description, &dx.pointerTextureShaderResource); 233 | if (IS_ERROR(result)) throw Error{result, "Failed to create pointer shader resource"}; 234 | } 235 | 236 | void WindowRenderer::updatePointerVertices(const PointerBuffer &pointer) { 237 | if (pointer.position_timestamp == m_lastPointerPositionUpdate) return; 238 | m_lastPointerPositionUpdate = pointer.position_timestamp; 239 | 240 | auto const &dx = *m_dx; 241 | 242 | auto vertices = std::array{ 243 | Vertex{-1.0f, -1.0f, 0.0f, 1.0f}, 244 | Vertex{-1.0f, 1.0f, 0.0f, 0.0f}, 245 | Vertex{1.0f, -1.0f, 1.0f, 1.0f}, 246 | Vertex{1.0f, -1.0f, 1.0f, 1.0f}, 247 | Vertex{-1.0f, 1.0f, 0.0f, 0.0f}, 248 | Vertex{1.0f, 1.0f, 1.0f, 0.0f}, 249 | }; 250 | auto texture_description = D3D11_TEXTURE2D_DESC{}; 251 | dx.backgroundTexture->GetDesc(&texture_description); 252 | 253 | auto const texture_size = 254 | SIZE{static_cast(texture_description.Width), static_cast(texture_description.Height)}; 255 | auto const center = POINT{texture_size.cx / 2, texture_size.cy / 2}; 256 | 257 | auto const position = pointer.position; 258 | 259 | auto const mouse_to_desktop = [&](Vertex &v, int x, int y) { 260 | v.x = (position.x + x - center.x) / static_cast(center.x); 261 | v.y = -1 * (position.y + y - center.y) / static_cast(center.y); 262 | }; 263 | 264 | auto const isMonochrome = pointer.shape_info.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME; 265 | auto const size = SIZE{ 266 | static_cast(pointer.shape_info.Width), 267 | static_cast(isMonochrome ? pointer.shape_info.Height / 2 : pointer.shape_info.Height), 268 | }; 269 | mouse_to_desktop(vertices[0], 0, size.cy); 270 | mouse_to_desktop(vertices[1], 0, 0); 271 | mouse_to_desktop(vertices[2], size.cx, size.cy); 272 | mouse_to_desktop(vertices[3], size.cx, size.cy); 273 | mouse_to_desktop(vertices[4], 0, 0); 274 | mouse_to_desktop(vertices[5], size.cx, 0); 275 | 276 | dx.deviceContext()->UpdateSubresource(dx.pointerVertexBuffer.Get(), 0, nullptr, &vertices[0], 0, 0); 277 | } 278 | 279 | WindowRenderer::Resources::Resources(WindowRenderer::InitArgs &&args) 280 | : BaseRenderer(std::move(args.basic)) 281 | , backgroundTexture(std::move(args.texture)) { 282 | 283 | createBackgroundTextureShaderResource(); 284 | createBackgroundVertexBuffer(); 285 | createSwapChain(args.windowHandle); 286 | createRenderTarget(); 287 | createMaskedPixelShader(); 288 | createLinearSamplerState(); 289 | createPointerVertexBuffer(); 290 | } 291 | 292 | void WindowRenderer::Resources::createBackgroundTextureShaderResource() { 293 | auto const result = 294 | device()->CreateShaderResourceView(backgroundTexture.Get(), nullptr, &backgroundTextureShaderResource); 295 | if (IS_ERROR(result)) throw Error{result, "Failed to create background shader resource"}; 296 | } 297 | 298 | void WindowRenderer::Resources::createBackgroundVertexBuffer() { 299 | static auto const vertices = std::array{ 300 | Vertex{-1.0f, -1.0f, 0.0f, 1.0f}, 301 | Vertex{-1.0f, 1.0f, 0.0f, 0.0f}, 302 | Vertex{1.0f, -1.0f, 1.0f, 1.0f}, 303 | Vertex{1.0f, -1.0f, 1.0f, 1.0f}, 304 | Vertex{-1.0f, 1.0f, 0.0f, 0.0f}, 305 | Vertex{1.0f, 1.0f, 1.0f, 0.0f}, 306 | }; 307 | auto const buffer_description = D3D11_BUFFER_DESC{ 308 | .ByteWidth = sizeof(vertices), 309 | .Usage = D3D11_USAGE_DEFAULT, 310 | .BindFlags = D3D11_BIND_VERTEX_BUFFER, 311 | .CPUAccessFlags = 0, 312 | .MiscFlags = {}, 313 | .StructureByteStride = {}, 314 | }; 315 | auto const init_data = D3D11_SUBRESOURCE_DATA{ 316 | .pSysMem = vertices.data(), 317 | .SysMemPitch = {}, 318 | .SysMemSlicePitch = {}, 319 | }; 320 | auto const result = device()->CreateBuffer(&buffer_description, &init_data, &backgroundVertexBuffer); 321 | if (IS_ERROR(result)) throw Error{result, "Failed to create vertex buffer"}; 322 | } 323 | 324 | void WindowRenderer::Resources::createSwapChain(HWND windowHandle) { 325 | auto factory = renderer::getFactory(device()); 326 | swapChain = renderer::createSwapChain(factory, device(), windowHandle); 327 | } 328 | 329 | void WindowRenderer::Resources::createRenderTarget() { 330 | auto back_buffer = ComPtr{}; 331 | auto const buffer = 0; 332 | auto result = swapChain->GetBuffer(buffer, __uuidof(ID3D11Texture2D), &back_buffer); 333 | if (IS_ERROR(result)) throw Error{result, "Failed to get backbuffer"}; 334 | 335 | static constexpr const D3D11_RENDER_TARGET_VIEW_DESC *render_target_description = nullptr; 336 | result = device()->CreateRenderTargetView(back_buffer.Get(), nullptr, &renderTarget); 337 | if (IS_ERROR(result)) throw Error{result, "Failed to create render target for backbuffer"}; 338 | } 339 | 340 | void WindowRenderer::Resources::createMaskedPixelShader() { 341 | auto const size = ARRAYSIZE(g_MaskedPixelShader); 342 | auto shader = &g_MaskedPixelShader[0]; 343 | ID3D11ClassLinkage *linkage = nullptr; 344 | auto const result = device()->CreatePixelShader(shader, size, linkage, &maskedPixelShader); 345 | if (IS_ERROR(result)) throw Error{result, "Failed to create pixel shader"}; 346 | } 347 | 348 | void WindowRenderer::Resources::createLinearSamplerState() { linearSamplerState = createLinearSampler(); } 349 | 350 | void WindowRenderer::Resources::createPointerVertexBuffer() { 351 | auto buffer_description = D3D11_BUFFER_DESC{ 352 | .ByteWidth = 6 * sizeof(Vertex), 353 | .Usage = D3D11_USAGE_DEFAULT, 354 | .BindFlags = D3D11_BIND_VERTEX_BUFFER, 355 | .CPUAccessFlags = 0, 356 | .MiscFlags = {}, 357 | .StructureByteStride = {}, 358 | }; 359 | auto const result = device()->CreateBuffer(&buffer_description, nullptr, &pointerVertexBuffer); 360 | if (IS_ERROR(result)) throw Error{result, "Failed to create vertex buffer"}; 361 | } 362 | -------------------------------------------------------------------------------- /src/MainApplication.cpp: -------------------------------------------------------------------------------- 1 | #include "MainApplication.h" 2 | 3 | #include "Model.h" 4 | #include "meta/scope_guard.h" 5 | 6 | #include 7 | 8 | namespace deskdup { 9 | 10 | using win32::Dimension; 11 | using win32::DisplayMonitor; 12 | using win32::WindowClass; 13 | 14 | namespace { 15 | 16 | static auto windowWorkArea() noexcept -> Rect { 17 | auto rect = RECT{}; 18 | ::SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0); 19 | return Rect::fromRECT(rect); 20 | } 21 | 22 | auto createWindowClass(HINSTANCE instanceHandle) -> WindowClass::Config { 23 | const auto cursor = ::LoadCursor(nullptr, IDC_CROSS); 24 | if (!cursor) throw Unexpected{"LoadCursor failed"}; 25 | LATER(::DestroyCursor(cursor)); 26 | 27 | const auto icon = ::LoadIconW(instanceHandle, L"desk1"); 28 | 29 | auto config = WindowClass::Config{}; 30 | config.instanceHandle = instanceHandle; 31 | config.className = L"deskdupl"; 32 | config.icon = win32::Icon{icon}; 33 | config.smallIcon = win32::Icon{icon}; 34 | config.cursor = win32::Cursor{cursor}; 35 | return config; 36 | } 37 | 38 | auto defaultConfig() -> Config { 39 | auto config = Config{}; 40 | config.isCaptureAreaShown = true; 41 | const auto workArea = windowWorkArea(); 42 | config.outputTopLeft = Point{workArea.topLeft.x + workArea.dimension.width / 2, workArea.topLeft.y + 32}; 43 | config.outputDimension = Dimension{workArea.dimension.width / 2, workArea.dimension.height / 2}; 44 | return config; 45 | } 46 | 47 | } // namespace 48 | 49 | MainApplication::MainApplication(HINSTANCE instanceHandle) 50 | : m_state{ 51 | .config = defaultConfig(), 52 | } 53 | , m_mainThread{createWindowClass(instanceHandle)} { 54 | 55 | m_state.duplicationStatus = 56 | m_state.config.operationMode == OperationMode::CaptureArea ? DuplicationStatus::Pause : DuplicationStatus::Live; 57 | 58 | refreshMonitors(); 59 | } 60 | 61 | int MainApplication::run() { 62 | try { 63 | m_powerRequest = PowerRequest{L"Desktop Duplication Tool"}; 64 | 65 | m_outputWindow.emplace(OutputWindow::Args{ 66 | .windowClass = m_mainThread.windowClass(), 67 | .mainController = *this, 68 | }); 69 | m_captureAreaWindow.emplace(CaptureAreaWindow::Args{ 70 | .windowClass = m_mainThread.windowClass(), 71 | .rect = operatonModeLens().captureAreaRect(), 72 | }); 73 | m_duplicationController.emplace(DuplicationController::Args{ 74 | .windowClass = m_mainThread.windowClass(), 75 | .mainController = *this, 76 | .mainThread = m_mainThread.thread(), 77 | .outputWindow = m_outputWindow->window(), 78 | }); 79 | 80 | m_outputWindow->show(m_state.outputMaximized); 81 | if (m_captureAreaWindow) { 82 | m_captureAreaWindow->updateIsShown(m_state.config.isCaptureAreaShown); 83 | m_captureAreaWindow->updateDuplicationStatus(m_state.duplicationStatus); 84 | } 85 | m_outputWindow->updateDuplicationStatus(m_state.duplicationStatus, m_state.config.pauseRendering); 86 | if (m_duplicationController) m_duplicationController->updateDuplicationStatus(m_state.duplicationStatus); 87 | 88 | return m_mainThread.run(); 89 | } 90 | catch (const Unexpected &e) { 91 | OutputDebugStringA(e.text); 92 | OutputDebugStringA("\n"); 93 | return -1; 94 | } 95 | } 96 | 97 | void MainApplication::quit() { PostQuitMessage(0); } 98 | 99 | void MainApplication::changeOperationMode(OperationMode operationMode) { 100 | if (m_state.config.operationMode != operationMode) { 101 | m_state.config.operationMode = operationMode; 102 | if (m_outputWindow) m_outputWindow->updateOperationMode(operationMode); 103 | if (m_captureAreaWindow) m_captureAreaWindow->updateRect(operatonModeLens().captureAreaRect()); 104 | if (m_duplicationController) { 105 | m_duplicationController->updateCaptureOffset(operatonModeLens().captureOffset()); 106 | m_duplicationController->updateOutputZoom(operatonModeLens().outputZoom()); 107 | } 108 | if (operationMode == OperationMode::CaptureArea) { 109 | m_state.outputMonitor = m_state.config.captureMonitor; 110 | updateCaptureAreaOutputScreen(); 111 | } 112 | if (operationMode == OperationMode::PresentMirror) { 113 | if (m_state.outputMonitor != m_state.config.captureMonitor) { 114 | if (m_state.duplicationStatus == DuplicationStatus::Live && m_duplicationController) 115 | m_duplicationController->restart(); 116 | updateScreenRect(m_state.monitors[m_state.config.captureMonitor].info.monitorRect); 117 | } 118 | } 119 | } 120 | } 121 | 122 | void MainApplication::updateSystemStatus(SystemStatus systemStatus) { 123 | if (m_state.systemStatus != systemStatus) { 124 | m_state.systemStatus = systemStatus; 125 | if (m_outputWindow) m_outputWindow->taskbarButtons().updateSystemStatus(systemStatus); 126 | } 127 | } 128 | 129 | void MainApplication::updateScreenRect(Rect rect) { 130 | if (m_state.captureMonitorRect != rect) { 131 | m_state.captureMonitorRect = rect; 132 | if (config().operationMode == OperationMode::PresentMirror && m_captureAreaWindow) 133 | m_captureAreaWindow->updateRect(operatonModeLens().captureAreaRect()); 134 | if (config().operationMode == OperationMode::CaptureArea && m_duplicationController) 135 | m_duplicationController->updateCaptureOffset(operatonModeLens().captureOffset()); 136 | } 137 | } 138 | 139 | void MainApplication::captureScreen(int screen) { 140 | if (config().operationMode != OperationMode::PresentMirror) return; // not used in this mode 141 | if (m_state.config.captureMonitor != screen && static_cast(m_state.monitors.size()) > screen) { 142 | m_state.config.captureMonitor = screen; 143 | if (m_state.duplicationStatus == DuplicationStatus::Live && m_duplicationController) 144 | m_duplicationController->restart(); 145 | updateScreenRect(m_state.monitors[m_state.config.captureMonitor].info.monitorRect); 146 | } 147 | } 148 | 149 | void MainApplication::updateOutputRect(Rect rect) { 150 | auto dimensionChanged = m_state.config.outputDimension != rect.dimension; 151 | auto topLeftChanged = m_state.config.outputTopLeft != rect.topLeft; 152 | if (dimensionChanged || topLeftChanged) { 153 | m_state.config.outputDimension = rect.dimension; 154 | m_state.config.outputTopLeft = rect.topLeft; 155 | if (m_outputWindow) m_outputWindow->updateRect(m_state.config.outputRect()); 156 | if (config().operationMode == OperationMode::PresentMirror) { 157 | if (dimensionChanged && m_captureAreaWindow) 158 | m_captureAreaWindow->updateRect(operatonModeLens().captureAreaRect()); 159 | } 160 | if (dimensionChanged && m_duplicationController) 161 | m_duplicationController->updateOutputDimension(m_state.config.outputDimension); 162 | if (config().operationMode == OperationMode::CaptureArea) { 163 | if (m_captureAreaWindow) m_captureAreaWindow->updateRect(operatonModeLens().captureAreaRect()); 164 | if (topLeftChanged && m_duplicationController) 165 | m_duplicationController->updateCaptureOffset(operatonModeLens().captureOffset()); 166 | 167 | updateCaptureAreaOutputScreen(); 168 | } 169 | } 170 | } 171 | 172 | void MainApplication::resizeOutputWindow(Dimension dimension, bool isMaximized) { 173 | if (m_state.config.outputDimension != dimension) { 174 | m_state.config.outputDimension = dimension; 175 | if (m_outputWindow) m_outputWindow->updateRect(m_state.config.outputRect()); 176 | if (m_captureAreaWindow) m_captureAreaWindow->updateRect(operatonModeLens().captureAreaRect()); 177 | if (m_duplicationController) m_duplicationController->updateOutputDimension(m_state.config.outputDimension); 178 | } 179 | if (m_state.outputMaximized != isMaximized) { 180 | m_state.outputMaximized = isMaximized; 181 | if (m_outputWindow) m_outputWindow->updateMaximized(m_state.outputMaximized); 182 | // updatePowerRequest 183 | const auto success = isMaximized ? m_powerRequest.set() : m_powerRequest.clear(); 184 | if (!success) { 185 | OutputDebugStringA("Power Request Failed!\n"); 186 | } 187 | } 188 | } 189 | 190 | void MainApplication::moveOutputWindowTo(Point topLeft) { 191 | if (topLeft != m_state.config.outputTopLeft) { 192 | m_state.config.outputTopLeft = topLeft; 193 | // note: on maximize the is triggered wrongly 194 | // if (m_outputWindow) m_outputWindow->updateRect(m_state.config.outputRect()); 195 | if (config().operationMode == OperationMode::CaptureArea) { 196 | if (m_captureAreaWindow) m_captureAreaWindow->updateRect(operatonModeLens().captureAreaRect()); 197 | if (m_duplicationController) 198 | m_duplicationController->updateCaptureOffset(operatonModeLens().captureOffset()); 199 | 200 | updateCaptureAreaOutputScreen(); 201 | } 202 | } 203 | } 204 | 205 | void MainApplication::moveCaptureOffsetByScreen(int dx, int dy) { 206 | if (config().operationMode != OperationMode::PresentMirror) return; // not used in this mode 207 | if (dx != 0 || dy != 0) { 208 | m_state.config.captureOffset.x += static_cast(dx) / m_state.config.outputZoom; 209 | m_state.config.captureOffset.y += static_cast(dy) / m_state.config.outputZoom; 210 | m_captureAreaWindow->updateRect(operatonModeLens().captureAreaRect()); 211 | if (m_duplicationController) m_duplicationController->updateCaptureOffset(operatonModeLens().captureOffset()); 212 | } 213 | } 214 | 215 | void MainApplication::resetOutputZoom() { 216 | if (config().operationMode != OperationMode::PresentMirror) return; // not used in this mode 217 | if (m_state.config.outputZoom != 1.0f) { 218 | m_state.config.outputZoom = 1.0f; 219 | m_captureAreaWindow->updateRect(operatonModeLens().captureAreaRect()); 220 | if (m_duplicationController) m_duplicationController->updateOutputZoom(operatonModeLens().outputZoom()); 221 | } 222 | } 223 | 224 | void MainApplication::zoomOutputBy(float delta) { 225 | if (config().operationMode != OperationMode::PresentMirror) return; // not used in this mode 226 | if (delta >= 0.0001f || delta <= 0.0001f) { 227 | m_state.config.outputZoom += delta; 228 | m_captureAreaWindow->updateRect(operatonModeLens().captureAreaRect()); 229 | if (m_duplicationController) m_duplicationController->updateOutputZoom(operatonModeLens().outputZoom()); 230 | } 231 | } 232 | 233 | void MainApplication::zoomOutputAt(Point point, float delta) { 234 | if (config().operationMode != OperationMode::PresentMirror) return; // not used in this mode 235 | if (delta >= 0.0001f || delta <= 0.0001f) { 236 | auto mx = point.x - m_state.config.outputTopLeft.x; 237 | auto my = point.y - m_state.config.outputTopLeft.y; 238 | auto offsetPoint = Vec2f{ 239 | static_cast(mx / m_state.config.outputZoom), 240 | static_cast(my / m_state.config.outputZoom), 241 | }; 242 | m_state.config.outputZoom += delta; 243 | m_state.config.captureOffset.x -= offsetPoint.x - static_cast(mx) / m_state.config.outputZoom; 244 | m_state.config.captureOffset.y -= offsetPoint.y - static_cast(my) / m_state.config.outputZoom; 245 | m_captureAreaWindow->updateRect(operatonModeLens().captureAreaRect()); 246 | if (m_duplicationController) { 247 | m_duplicationController->updateCaptureOffset(operatonModeLens().captureOffset()); 248 | m_duplicationController->updateOutputZoom(operatonModeLens().outputZoom()); 249 | } 250 | } 251 | } 252 | 253 | void MainApplication::toggleVisualizeCaptureArea() { 254 | auto isShown = (m_state.config.isCaptureAreaShown = !m_state.config.isCaptureAreaShown); 255 | m_outputWindow->taskbarButtons().updateVisibleArea(isShown); 256 | m_captureAreaWindow->updateIsShown(isShown); 257 | } 258 | 259 | void MainApplication::changeDuplicationStatus(DuplicationStatus status) { 260 | if (m_state.duplicationStatus != status) { 261 | m_state.duplicationStatus = status; 262 | if (m_outputWindow) 263 | m_outputWindow->updateDuplicationStatus(m_state.duplicationStatus, m_state.config.pauseRendering); 264 | if (m_captureAreaWindow) m_captureAreaWindow->updateDuplicationStatus(m_state.duplicationStatus); 265 | if (m_duplicationController) m_duplicationController->updateDuplicationStatus(m_state.duplicationStatus); 266 | } 267 | } 268 | 269 | void MainApplication::toggleOutputMaximize() { 270 | m_outputWindow->show(!m_state.outputMaximized); // note: this triggers a maximized event 271 | } 272 | 273 | void MainApplication::refreshMonitors() { 274 | resetMonitors(); 275 | if (m_state.config.operationMode == OperationMode::PresentMirror) { 276 | if (m_state.config.captureMonitor >= static_cast(m_state.monitors.size())) { 277 | captureScreen(0); 278 | } 279 | else { 280 | updateScreenRect(m_state.monitors[m_state.config.captureMonitor].info.monitorRect); 281 | } 282 | } 283 | auto outputSample = Point{ 284 | m_state.config.outputTopLeft.x + m_state.config.outputDimension.width / 2, m_state.config.outputTopLeft.y}; 285 | auto isOutputVisible = std::any_of(m_state.monitors.begin(), m_state.monitors.end(), [&](Monitor const &m) { 286 | return m.info.monitorRect.contains(outputSample); 287 | }); 288 | if (!isOutputVisible) { 289 | auto workArea = m_state.monitors[0].info.workRect; 290 | updateOutputRect(Rect{ 291 | Point{workArea.topLeft.x + workArea.dimension.width / 2, workArea.topLeft.y + 32}, 292 | Dimension{workArea.dimension.width / 2, workArea.dimension.height / 2}}); 293 | } 294 | } 295 | 296 | bool MainApplication::updateCaptureAreaOutputScreen() { 297 | auto dm = DisplayMonitor::fromRect(m_state.config.outputRect()); 298 | if (dm.handle() != m_state.monitors[m_state.outputMonitor].handle) { 299 | m_state.outputMonitor = m_state.computeIndexForMonitorHandle(dm.handle()); 300 | updateScreenRect(m_state.monitors[m_state.outputMonitor].info.monitorRect); 301 | if (m_duplicationController) { 302 | m_duplicationController->restart(); 303 | } 304 | return true; 305 | } 306 | return false; 307 | } 308 | 309 | void MainApplication::resetMonitors() { 310 | auto monitors = Monitors{}; 311 | DisplayMonitor::enumerateAll([&](DisplayMonitor dm, Rect) { 312 | monitors.push_back({ 313 | .handle = dm.handle(), 314 | .info = dm.monitorInfo(), 315 | }); 316 | }); 317 | if (monitors.empty()) { 318 | OutputDebugStringA("No Monitors found!"); 319 | std::exit(9); 320 | } 321 | if (monitors != m_state.monitors) { 322 | m_state.monitors = monitors; 323 | } 324 | } 325 | 326 | } // namespace deskdup 327 | --------------------------------------------------------------------------------