SelectRectangle.cpp
1 //============================================================================== 2 // 3 // Zoomit 4 // Sysinternals - www.sysinternals.com 5 // 6 // Class to select a recording rectangle and show it while recording 7 // 8 //============================================================================== 9 #include "pch.h" 10 #include "SelectRectangle.h" 11 #include "Utility.h" 12 #include "WindowsVersions.h" 13 14 //---------------------------------------------------------------------------- 15 // 16 // SelectRectangle::Start 17 // 18 //---------------------------------------------------------------------------- 19 bool SelectRectangle::Start( HWND ownerWindow, bool fullMonitor ) 20 { 21 WNDCLASSW windowClass{}; 22 windowClass.lpfnWndProc = []( HWND window, UINT message, WPARAM wordParam, LPARAM longParam ) -> LRESULT 23 { 24 if( message == WM_NCCREATE ) 25 { 26 auto createStruct = reinterpret_cast<LPCREATESTRUCT>(longParam); 27 SetWindowLongPtrW( window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(createStruct->lpCreateParams) ); 28 return TRUE; 29 } 30 31 auto self = reinterpret_cast<SelectRectangle*>(GetWindowLongPtrW( window, GWLP_USERDATA )); 32 return self->WindowProc( window, message, wordParam, longParam ); 33 }; 34 windowClass.hInstance = GetModuleHandle( nullptr ); 35 windowClass.hCursor = LoadCursorW( nullptr, IDC_CROSS ); 36 windowClass.hbrBackground = static_cast<HBRUSH>(GetStockObject( BLACK_BRUSH )); 37 windowClass.lpszClassName = m_className; 38 if( RegisterClassW( &windowClass ) == 0 ) 39 { 40 THROW_LAST_ERROR_IF( GetLastError() != ERROR_CLASS_ALREADY_EXISTS ); 41 42 WNDCLASSW existingClass{}; 43 THROW_IF_WIN32_BOOL_FALSE( GetClassInfoW( GetModuleHandle( nullptr ), m_className, &existingClass ) ); 44 THROW_LAST_ERROR_IF( existingClass.lpfnWndProc != windowClass.lpfnWndProc ); 45 } 46 47 m_cancel = false; 48 auto rect = GetMonitorRectFromCursor(); 49 m_window = wil::unique_hwnd( CreateWindowExW( WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST, m_className, nullptr, WS_POPUP, 50 rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, ownerWindow, 51 nullptr, nullptr, this ) ); 52 THROW_LAST_ERROR_IF_NULL( m_window.get() ); 53 54 if( fullMonitor ) 55 { 56 m_selectedRect = rect; 57 ShowSelected(); 58 } 59 else 60 { 61 SetLayeredWindowAttributes( m_window.get(), 0, Alpha(), LWA_ALPHA ); 62 } 63 64 ShowWindow( m_window.get(), SW_SHOW ); 65 SetForegroundWindow( m_window.get() ); 66 67 if( !fullMonitor ) 68 { 69 GetClipCursor( &m_oldClipRect ); 70 ClipCursor( &rect ); 71 m_setClip = true; 72 } 73 74 MSG message; 75 while( GetMessageW( &message, nullptr, 0, 0 ) != 0 ) 76 { 77 TranslateMessage( &message ); 78 DispatchMessageW( &message ); 79 if( m_cancel ) 80 { 81 return false; 82 } 83 if( m_selected ) 84 { 85 break; 86 } 87 } 88 return true; 89 } 90 91 //---------------------------------------------------------------------------- 92 // 93 // SelectRectangle::Stop 94 // 95 //---------------------------------------------------------------------------- 96 void SelectRectangle::Stop() 97 { 98 if( m_setClip ) 99 { 100 ClipCursor( &m_oldClipRect ); 101 m_setClip = false; 102 } 103 m_window.reset(); 104 m_selected = false; 105 m_selectedRect = {}; 106 m_cancel = true; 107 } 108 109 //---------------------------------------------------------------------------- 110 // 111 // SelectRectangle::ShowSelected 112 // 113 //---------------------------------------------------------------------------- 114 void SelectRectangle::ShowSelected() 115 { 116 m_selected = true; 117 118 // Set the alpha to match the Windows graphics capture API yellow border 119 // and set the window to be transparent and disabled, so it will be skipped 120 // for hit testing and as a candidate for the next foreground window. 121 SetLayeredWindowAttributes( m_window.get(), 0, 191, LWA_ALPHA ); 122 SetWindowLong( m_window.get(), GWL_EXSTYLE, GetWindowLong( m_window.get(), GWL_EXSTYLE ) | WS_EX_TRANSPARENT ); 123 EnableWindow( m_window.get(), FALSE ); 124 125 POINT point{ m_selectedRect.left, m_selectedRect.top }; 126 auto rect = m_selectedRect; 127 OffsetRect( &rect, -rect.left, -rect.top ); 128 int width = ScaleForDpi( 2, m_dpi ); 129 130 // Draw the selection border outside the selected rectangle on builds lower 131 // than Windows 11 22H2 because the graphics capture API does not skip 132 // windows if layered, meaning this yellow border will be captured. 133 if( GetWindowsBuild( nullptr ) < BUILD_WINDOWS_11_22H2 ) 134 { 135 InflateRect( &rect, width, width ); 136 OffsetRect( &rect, -rect.left, -rect.top ); 137 point.x -= width; 138 point.y -= width; 139 } 140 141 // Resize the window to the selection rectangle and translate the position. 142 RECT windowRect; 143 GetWindowRect( m_window.get(), &windowRect ); 144 point.x += windowRect.left; 145 point.y += windowRect.top; 146 MoveWindow( m_window.get(), point.x, point.y, rect.right, rect.bottom, true ); 147 148 // Use a region to keep everything but the border transparent. 149 wil::unique_hrgn region{CreateRectRgnIndirect( &rect )}; 150 InflateRect( &rect, -width, -width ); 151 wil::unique_hrgn insideRegion{CreateRectRgnIndirect( &rect )}; 152 CombineRgn( region.get(), region.get(), insideRegion.get(), RGN_XOR ); 153 SetWindowRgn( m_window.get(), region.release(), true ); 154 } 155 156 //---------------------------------------------------------------------------- 157 // 158 // SelectRectangle::UpdateOwner 159 // 160 //---------------------------------------------------------------------------- 161 void SelectRectangle::UpdateOwner( HWND window ) 162 { 163 if( m_window != nullptr ) 164 { 165 SetWindowLongPtr( m_window.get(), GWLP_HWNDPARENT, reinterpret_cast<LONG_PTR>(window) ); 166 SetWindowPos( m_window.get(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE ); 167 } 168 } 169 170 //---------------------------------------------------------------------------- 171 // 172 // SelectRectangle::WindowProc 173 // 174 //---------------------------------------------------------------------------- 175 LRESULT SelectRectangle::WindowProc( HWND window, UINT message, WPARAM wordParam, LPARAM longParam ) 176 { 177 switch( message ) 178 { 179 case WM_CREATE: 180 m_dpi = GetDpiForWindowHelper( window ); 181 SetWindowDisplayAffinity( window, WDA_EXCLUDEFROMCAPTURE ); 182 return 0; 183 184 case WM_DESTROY: 185 Stop(); 186 return 0; 187 188 case WM_LBUTTONDOWN: 189 { 190 SetCapture( window ); 191 192 m_startPoint = { GET_X_LPARAM( longParam ), GET_Y_LPARAM( longParam ) }; 193 [[fallthrough]]; 194 } 195 case WM_MOUSEMOVE: 196 if( GetCapture() == window ) 197 { 198 RECT rect; 199 GetClientRect( window, &rect ); 200 POINT point{ GET_X_LPARAM( longParam ), GET_Y_LPARAM( longParam ) }; 201 m_selectedRect = ForceRectInBounds( RectFromPointsMinSize( m_startPoint, point, MinSize() ), rect ); 202 203 // Use a region to carve out the selected rectangle. 204 wil::unique_hrgn region{CreateRectRgnIndirect( &m_selectedRect )}; 205 wil::unique_hrgn clientRegion{CreateRectRgnIndirect( &rect )}; 206 CombineRgn( region.get(), region.get(), clientRegion.get(), RGN_XOR ); 207 SetWindowRgn( window, region.release(), true ); 208 } 209 return 0; 210 211 case WM_KEYDOWN: 212 if( wordParam == VK_ESCAPE ) 213 { 214 Stop(); 215 } 216 return 0; 217 218 case WM_KILLFOCUS: 219 if( !m_selected ) 220 { 221 Stop(); 222 } 223 return 0; 224 225 case WM_LBUTTONUP: 226 { 227 if( m_setClip ) 228 { 229 ClipCursor( &m_oldClipRect ); 230 m_setClip = false; 231 } 232 ReleaseCapture(); 233 234 ShowSelected(); 235 return 0; 236 } 237 case WM_NCHITTEST: 238 if( m_selected ) 239 { 240 return HTTRANSPARENT; 241 } 242 break; 243 244 case WM_PAINT: 245 if( m_selected ) 246 { 247 PAINTSTRUCT paint; 248 auto deviceContext = BeginPaint( window, &paint ); 249 250 RECT rect; 251 GetClientRect( window, &rect ); 252 253 // Draw a border matching the Windows graphics capture API border. 254 // The outer frame is yellow and two logical pixels wide, while the 255 // inner is black and 1 logical pixel wide. 256 wil::unique_hbrush brush{CreateSolidBrush( RGB( 255, 222, 0 ) )}; 257 FillRect( deviceContext, &rect, brush.get() ); 258 int width = ScaleForDpi( 1, m_dpi ); 259 InflateRect( &rect, -width, -width ); 260 FillRect( deviceContext, &rect, static_cast<HBRUSH>(GetStockObject( BLACK_BRUSH )) ); 261 262 EndPaint( window, &paint ); 263 return 0; 264 } 265 break; 266 } 267 268 return DefWindowProcW( window, message, wordParam, longParam ); 269 }