/ src / modules / ZoomIt / ZoomIt / SelectRectangle.cpp
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  }