PowerToys.MeasureToolCore.cpp
1 #include "pch.h" 2 3 #include <common/display/dpi_aware.h> 4 #include <common/display/monitors.h> 5 #include <common/utils/logger_helper.h> 6 #include <common/utils/UnhandledExceptionHandler.h> 7 #include <common/logger/logger.h> 8 9 #include "../MeasureToolModuleInterface/trace.h" 10 #include "constants.h" 11 #include "PowerToys.MeasureToolCore.h" 12 #include "Core.g.cpp" 13 #include "OverlayUI.h" 14 #include "ScreenCapturing.h" 15 16 //#define DEBUG_PRIMARY_MONITOR_ONLY 17 18 namespace winrt::PowerToys::MeasureToolCore::implementation 19 { 20 void Core::MouseCaptureThread() 21 { 22 while (!_stopMouseCaptureThreadSignal.is_signaled()) 23 { 24 static_assert(sizeof(_commonState.cursorPosSystemSpace) == sizeof(LONG64)); 25 POINT cursorPos = {}; 26 GetCursorPos(&cursorPos); 27 InterlockedExchange64(reinterpret_cast<LONG64*>(&_commonState.cursorPosSystemSpace), std::bit_cast<LONG64>(cursorPos)); 28 std::this_thread::sleep_for(consts::TARGET_FRAME_DURATION); 29 } 30 } 31 32 Core::Core() : 33 _stopMouseCaptureThreadSignal{ wil::EventOptions::ManualReset }, 34 _mouseCaptureThread{ [this] { MouseCaptureThread(); } } 35 { 36 } 37 38 Core::~Core() 39 { 40 Close(); 41 } 42 43 void Core::Close() 44 { 45 ResetState(); 46 47 // avoid triggering d2d debug layer leak on shutdown 48 dxgiAPI = DxgiAPI{ DxgiAPI::Uninitialized{} }; 49 50 #if 0 51 winrt::com_ptr<IDXGIDebug> dxgiDebug; 52 winrt::check_hresult(DXGIGetDebugInterface1({}, 53 winrt::guid_of<IDXGIDebug>(), 54 dxgiDebug.put_void())); 55 dxgiDebug->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_ALL); 56 #endif 57 58 if (!_stopMouseCaptureThreadSignal.is_signaled()) 59 _stopMouseCaptureThreadSignal.SetEvent(); 60 61 if (_mouseCaptureThread.joinable()) 62 _mouseCaptureThread.join(); 63 } 64 65 void Core::InitResources() 66 { 67 Measurement::InitResources(); 68 } 69 70 void Core::ResetState() 71 { 72 _commonState.closeOnOtherMonitors = true; 73 _overlayUIStates.clear(); 74 _boundsToolState = { .commonState = &_commonState }; 75 for (auto& thread : _screenCaptureThreads) 76 { 77 if (thread.joinable()) 78 { 79 thread.join(); 80 } 81 } 82 _screenCaptureThreads.clear(); 83 _measureToolState.Reset(); 84 _measureToolState.Access([&](MeasureToolState& s) { 85 s.commonState = &_commonState; 86 }); 87 88 _settings = Settings::LoadFromFile(); 89 90 _commonState.units = _settings.units; 91 _commonState.lineColor.r = _settings.lineColor[0] / 255.f; 92 _commonState.lineColor.g = _settings.lineColor[1] / 255.f; 93 _commonState.lineColor.b = _settings.lineColor[2] / 255.f; 94 _commonState.closeOnOtherMonitors = false; 95 } 96 97 void Core::StartBoundsTool() 98 { 99 ResetState(); 100 101 #if defined(DEBUG_PRIMARY_MONITOR_ONLY) 102 std::vector<MonitorInfo> monitors = { MonitorInfo::GetPrimaryMonitor() }; 103 const auto& monitorInfo = monitors[0]; 104 #else 105 const auto monitors = MonitorInfo::GetMonitors(true); 106 for (const auto& monitorInfo : monitors) 107 #endif 108 { 109 auto overlayUI = OverlayUIState::Create(&dxgiAPI, 110 _boundsToolState, 111 _commonState, 112 monitorInfo); 113 #if !defined(DEBUG_PRIMARY_MONITOR_ONLY) 114 if (!overlayUI) 115 continue; 116 #endif 117 _overlayUIStates.push_back(std::move(overlayUI)); 118 } 119 120 trace.UpdateState(true); 121 Trace::BoundsToolActivated(); 122 trace.Flush(); 123 trace.UpdateState(false); 124 } 125 126 void Core::StartMeasureTool(const bool horizontal, const bool vertical) 127 { 128 ResetState(); 129 130 _measureToolState.Access([horizontal, vertical, this](MeasureToolState& state) { 131 if (horizontal) 132 state.global.mode = vertical ? MeasureToolState::Mode::Cross : MeasureToolState::Mode::Horizontal; 133 else 134 state.global.mode = MeasureToolState::Mode::Vertical; 135 136 state.global.continuousCapture = _settings.continuousCapture; 137 state.global.drawFeetOnCross = _settings.drawFeetOnCross; 138 state.global.pixelTolerance = _settings.pixelTolerance; 139 state.global.perColorChannelEdgeDetection = _settings.perColorChannelEdgeDetection; 140 }); 141 142 #if defined(DEBUG_PRIMARY_MONITOR_ONLY) 143 std::vector<MonitorInfo> monitors = { MonitorInfo::GetPrimaryMonitor() }; 144 const auto& monitorInfo = monitors[0]; 145 #else 146 const auto monitors = MonitorInfo::GetMonitors(true); 147 for (const auto& monitorInfo : monitors) 148 #endif 149 { 150 auto overlayUI = OverlayUIState::Create(&dxgiAPI, 151 _measureToolState, 152 _commonState, 153 monitorInfo); 154 #if !defined(DEBUG_PRIMARY_MONITOR_ONLY) 155 if (!overlayUI) 156 return; 157 #endif 158 _overlayUIStates.push_back(std::move(overlayUI)); 159 } 160 161 for (size_t i = 0; i < monitors.size(); ++i) 162 { 163 auto thread = StartCapturingThread( 164 &dxgiAPI, 165 _commonState, 166 _measureToolState, 167 _overlayUIStates[i]->overlayWindowHandle(), 168 monitors[i]); 169 _screenCaptureThreads.emplace_back(std::move(thread)); 170 } 171 172 trace.UpdateState(true); 173 Trace::MeasureToolActivated(); 174 trace.Flush(); 175 trace.UpdateState(false); 176 } 177 178 void MeasureToolCore::implementation::Core::SetToolCompletionEvent(ToolSessionCompleted sessionCompletedTrigger) 179 { 180 _commonState.sessionCompletedCallback = [trigger = std::move(sessionCompletedTrigger)] { 181 trigger(); 182 }; 183 } 184 185 void MeasureToolCore::implementation::Core::SetToolbarBoundingBox(const uint32_t fromX, 186 const uint32_t fromY, 187 const uint32_t toX, 188 const uint32_t toY) 189 { 190 _commonState.toolbarBoundingBox = Box{ RECT{ .left = static_cast<long>(fromX), 191 .top = static_cast<long>(fromY), 192 .right = static_cast<long>(toX), 193 .bottom = static_cast<long>(toY) } }; 194 } 195 196 float MeasureToolCore::implementation::Core::GetDPIScaleForWindow(uint64_t windowHandle) 197 { 198 UINT dpi = DPIAware::DEFAULT_DPI; 199 DPIAware::GetScreenDPIForWindow(std::bit_cast<HWND>(windowHandle), dpi); 200 return static_cast<float>(dpi) / DPIAware::DEFAULT_DPI; 201 } 202 }