/ src / modules / ZoomIt / ZoomIt / Zoomit.cpp
Zoomit.cpp
    1  //============================================================================
    2  //
    3  // Zoomit
    4  // Copyright (C) Mark Russinovich
    5  // Sysinternals - www.sysinternals.com
    6  //
    7  // Screen zoom and annotation tool.
    8  //
    9  // The Microsoft Corporation licenses this file to you under the MIT license.
   10  // See the LICENSE file in the project root for more information.
   11  //============================================================================
   12  #include "pch.h"
   13  
   14  #include "zoomit.h"
   15  #include "Utility.h"
   16  #include "WindowsVersions.h"
   17  #include "ZoomItSettings.h"
   18  #include "GifRecordingSession.h"
   19  
   20  #ifdef __ZOOMIT_POWERTOYS__
   21  #include <common/interop/shared_constants.h>
   22  #include <common/utils/ProcessWaiter.h>
   23  #include <common/utils/process_path.h>
   24  
   25  #include "../ZoomItModuleInterface/trace.h"
   26  #include <common/Telemetry/EtwTrace/EtwTrace.h>
   27  #include <common/logger/logger.h>
   28  #include <common/utils/logger_helper.h>
   29  #include <common/utils/winapi_error.h>
   30  #include <common/utils/gpo.h>
   31  #include <array>
   32  #include <vector>
   33  #endif // __ZOOMIT_POWERTOYS__
   34  
   35  #ifdef __ZOOMIT_POWERTOYS__
   36  enum class ZoomItCommand
   37  {
   38      Zoom,
   39      Draw,
   40      Break,
   41      LiveZoom,
   42      Snip,
   43      Record,
   44  };
   45  #endif // __ZOOMIT_POWERTOYS__
   46  
   47  namespace winrt
   48  {
   49      using namespace Windows::Foundation;
   50      using namespace Windows::Graphics;
   51      using namespace Windows::Graphics::Capture;
   52      using namespace Windows::Graphics::Imaging;
   53      using namespace Windows::Storage;
   54      using namespace Windows::UI::Composition;
   55      using namespace Windows::Storage::Pickers;
   56      using namespace Windows::System;
   57      using namespace Windows::Devices::Enumeration;
   58  }
   59  
   60  namespace util
   61  {
   62      using namespace robmikh::common::uwp;
   63      using namespace robmikh::common::desktop;
   64  }
   65  
   66  // This workaround keeps live zoom enabled after zooming out at level 1 (not zoomed) and disables
   67  // live zoom when recording is stopped
   68  #define WINDOWS_CURSOR_RECORDING_WORKAROUND 1
   69  
   70  HINSTANCE		g_hInstance;
   71  
   72  COLORREF	g_CustomColors[16];
   73  
   74  #define ZOOM_HOTKEY				0
   75  #define DRAW_HOTKEY				1
   76  #define BREAK_HOTKEY			2
   77  #define LIVE_HOTKEY				3
   78  #define LIVE_DRAW_HOTKEY		    4
   79  #define RECORD_HOTKEY		    5
   80  #define RECORD_CROP_HOTKEY	    6
   81  #define RECORD_WINDOW_HOTKEY	    7
   82  #define SNIP_HOTKEY			    8
   83  #define SNIP_SAVE_HOTKEY		    9
   84  #define DEMOTYPE_HOTKEY		    10
   85  #define DEMOTYPE_RESET_HOTKEY    11
   86  #define RECORD_GIF_HOTKEY        12
   87  #define RECORD_GIF_WINDOW_HOTKEY 13
   88  #define SAVE_IMAGE_HOTKEY        14
   89  #define SAVE_CROP_HOTKEY         15
   90  #define COPY_IMAGE_HOTKEY        16
   91  #define COPY_CROP_HOTKEY         17
   92  
   93  #define ZOOM_PAGE	  0
   94  #define LIVE_PAGE	  1
   95  #define DRAW_PAGE	  2
   96  #define TYPE_PAGE	  3
   97  #define DEMOTYPE_PAGE 4
   98  #define BREAK_PAGE	  5
   99  #define RECORD_PAGE	  6
  100  #define SNIP_PAGE	  7
  101  
  102  OPTION_TABS g_OptionsTabs[] = {
  103      { _T("Zoom"), NULL },
  104      { _T("LiveZoom"), NULL },
  105      { _T("Draw"), NULL },
  106      { _T("Type"), NULL },
  107      { _T("DemoType"), NULL },
  108      { _T("Break"), NULL },
  109      { _T("Record"), NULL },
  110      { _T("Snip"), NULL }
  111  };
  112  
  113  static const TCHAR* g_RecordingFormats[] = {
  114      _T("GIF"),
  115      _T("MP4")
  116  };
  117  
  118  float g_ZoomLevels[] = {
  119      1.25,
  120      1.50,
  121      1.75,
  122      2.00,
  123      3.00,
  124      4.00
  125  };
  126  
  127  DWORD g_FramerateOptions[] = {
  128      15,
  129      24,
  130      30,
  131      60
  132  };
  133  
  134  //
  135  // For typing mode
  136  //
  137  typedef enum {
  138      TypeModeOff = 0,
  139      TypeModeLeftJustify,
  140      TypeModeRightJustify
  141  } TypeModeState;
  142  
  143  const DWORD CURSOR_ARM_LENGTH = 4;
  144  
  145  const float NORMAL_BLUR_RADIUS = 20;
  146  const float STRONG_BLUR_RADIUS = 40;
  147  
  148  DWORD	g_ToggleMod;
  149  DWORD	g_LiveZoomToggleMod;
  150  DWORD	g_DrawToggleMod;
  151  DWORD	g_BreakToggleMod;
  152  DWORD	g_DemoTypeToggleMod;
  153  DWORD	g_RecordToggleMod;
  154  DWORD   g_SnipToggleMod;
  155  
  156  BOOLEAN	g_ZoomOnLiveZoom = FALSE;
  157  DWORD	g_PenWidth = PEN_WIDTH;
  158  float   g_BlurRadius = NORMAL_BLUR_RADIUS;
  159  HWND	hWndOptions = NULL;
  160  BOOLEAN	g_DrawPointer = FALSE;
  161  BOOLEAN g_PenDown = FALSE;
  162  BOOLEAN g_PenInverted = FALSE;
  163  DWORD	g_OsVersion;
  164  HWND	g_hWndLiveZoom = NULL;
  165  HWND    g_hWndLiveZoomMag = NULL;
  166  HWND	g_hWndMain;
  167  int		g_AlphaBlend = 0x80;
  168  BOOL	g_fullScreenWorkaround = FALSE;
  169  bool	g_bSaveInProgress = false;
  170  std::wstring	g_TextBuffer;
  171  // This is useful in the context of right-justified text only.
  172  std::list<std::wstring> g_TextBufferPreviousLines;
  173  #if WINDOWS_CURSOR_RECORDING_WORKAROUND
  174  bool	g_LiveZoomLevelOne = false;
  175  #endif
  176  
  177  // True if ZoomIt was started by PowerToys instead of standalone.
  178  BOOLEAN g_StartedByPowerToys = FALSE;
  179  BOOLEAN g_running = TRUE;
  180  
  181  // Screen recording globals
  182  #define DEFAULT_RECORDING_FILE		L"Recording.mp4"
  183  #define DEFAULT_GIF_RECORDING_FILE	L"Recording.gif"
  184  #define DEFAULT_SCREENSHOT_FILE		L"ZoomIt.png"
  185  
  186  BOOL	g_RecordToggle = FALSE;
  187  BOOL	g_RecordCropping = FALSE;
  188  SelectRectangle g_SelectRectangle;
  189  std::wstring	g_RecordingSaveLocation;
  190  std::wstring	g_ScreenshotSaveLocation;
  191  winrt::IDirect3DDevice	g_RecordDevice{ nullptr };
  192  std::shared_ptr<VideoRecordingSession> g_RecordingSession = nullptr;
  193  std::shared_ptr<GifRecordingSession> g_GifRecordingSession = nullptr;
  194  type_pGetMonitorInfo		pGetMonitorInfo;
  195  type_MonitorFromPoint		pMonitorFromPoint;
  196  type_pSHAutoComplete		pSHAutoComplete;
  197  type_pSetLayeredWindowAttributes	pSetLayeredWindowAttributes;
  198  type_pSetProcessDPIAware	pSetProcessDPIAware;
  199  type_pMagSetWindowSource	pMagSetWindowSource;
  200  type_pMagSetWindowTransform pMagSetWindowTransform;
  201  type_pMagSetFullscreenTransform pMagSetFullscreenTransform;
  202  type_pMagSetInputTransform	pMagSetInputTransform;
  203  type_pMagShowSystemCursor	pMagShowSystemCursor;
  204  type_pMagSetWindowFilterList pMagSetWindowFilterList;
  205  type_MagSetFullscreenUseBitmapSmoothing pMagSetFullscreenUseBitmapSmoothing;
  206  type_pMagSetLensUseBitmapSmoothing pMagSetLensUseBitmapSmoothing;
  207  type_pMagInitialize			pMagInitialize;
  208  type_pDwmIsCompositionEnabled	pDwmIsCompositionEnabled;
  209  type_pGetPointerType		pGetPointerType;
  210  type_pGetPointerPenInfo pGetPointerPenInfo;
  211  type_pSystemParametersInfoForDpi pSystemParametersInfoForDpi;
  212  type_pGetDpiForWindow		pGetDpiForWindow;
  213  
  214  type_pSHQueryUserNotificationState	pSHQueryUserNotificationState;
  215  
  216  type_pCreateDirect3D11DeviceFromDXGIDevice		pCreateDirect3D11DeviceFromDXGIDevice;
  217  type_pCreateDirect3D11SurfaceFromDXGISurface	pCreateDirect3D11SurfaceFromDXGISurface;
  218  type_pD3D11CreateDevice 						pD3D11CreateDevice;
  219  
  220  ClassRegistry	reg( _T("Software\\Sysinternals\\") APPNAME );
  221  
  222  ComputerGraphicsInit	g_GraphicsInit;
  223  
  224  
  225  // Event handler to set icon and extended style on dialog creation
  226  class OpenSaveDialogEvents : public IFileDialogEvents
  227  {
  228  public:
  229      OpenSaveDialogEvents(bool showOnTaskbar = true) : m_refCount(1), m_initialized(false), m_showOnTaskbar(showOnTaskbar) {}
  230  
  231      // IUnknown
  232      IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv)
  233      {
  234          static const QITAB qit[] = {
  235              QITABENT(OpenSaveDialogEvents, IFileDialogEvents),
  236              { 0 },
  237          };
  238          return QISearch(this, qit, riid, ppv);
  239      }
  240      IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_refCount); }
  241      IFACEMETHODIMP_(ULONG) Release()
  242      {
  243          ULONG count = InterlockedDecrement(&m_refCount);
  244          if (count == 0) delete this;
  245          return count;
  246      }
  247  
  248      // IFileDialogEvents
  249      IFACEMETHODIMP OnFileOk(IFileDialog*) { return S_OK; }
  250      IFACEMETHODIMP OnFolderChange(IFileDialog* pfd)
  251      {
  252          if (!m_initialized)
  253          {
  254              m_initialized = true;
  255              wil::com_ptr<IOleWindow> pWindow;
  256              if (SUCCEEDED(pfd->QueryInterface(IID_PPV_ARGS(&pWindow))))
  257              {
  258                  HWND hwndDialog = nullptr;
  259                  if (SUCCEEDED(pWindow->GetWindow(&hwndDialog)) && hwndDialog)
  260                  {
  261                      if (m_showOnTaskbar)
  262                      {
  263                          // Set WS_EX_APPWINDOW extended style
  264                          LONG_PTR exStyle = GetWindowLongPtr(hwndDialog, GWL_EXSTYLE);
  265                          SetWindowLongPtr(hwndDialog, GWL_EXSTYLE, exStyle | WS_EX_APPWINDOW);
  266                      }
  267  
  268                      // Set the dialog icon
  269                      HICON hIcon = LoadIcon(g_hInstance, L"APPICON");
  270                      if (hIcon)
  271                      {
  272                          SendMessage(hwndDialog, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIcon));
  273                          SendMessage(hwndDialog, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(hIcon));
  274                      }
  275                  }
  276              }
  277          }
  278          return S_OK;
  279      }
  280      IFACEMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) { return S_OK; }
  281      IFACEMETHODIMP OnSelectionChange(IFileDialog*) { return S_OK; }
  282      IFACEMETHODIMP OnShareViolation(IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) { return S_OK; }
  283      IFACEMETHODIMP OnTypeChange(IFileDialog*) { return S_OK; }
  284      IFACEMETHODIMP OnOverwrite(IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE*) { return S_OK; }
  285  
  286  private:
  287      LONG m_refCount;
  288      bool m_initialized;
  289      bool m_showOnTaskbar;
  290  };
  291  
  292  
  293  //----------------------------------------------------------------------------
  294  //
  295  // Saves specified filePath to clipboard.
  296  //
  297  //----------------------------------------------------------------------------
  298  bool SaveToClipboard( const WCHAR* filePath, HWND hwnd )
  299  {
  300      if( filePath == NULL || hwnd == NULL || wcslen( filePath ) == 0 )
  301      {
  302          return false;
  303      }
  304  
  305      size_t size = sizeof(DROPFILES) + sizeof(WCHAR) * ( _tcslen( filePath ) + 1 ) + sizeof(WCHAR);
  306  
  307      HDROP hDrop   = static_cast<HDROP>(GlobalAlloc( GHND, size ));
  308      if (hDrop == NULL)
  309      {
  310          return false;
  311      }
  312  
  313      DROPFILES* dFiles = static_cast<DROPFILES*>(GlobalLock( hDrop ));
  314      if (dFiles == NULL)
  315      {
  316          GlobalFree( hDrop );
  317          return false;
  318      }
  319  
  320      dFiles->pFiles = sizeof(DROPFILES);
  321      dFiles->fWide = TRUE;
  322  
  323      wcscpy( reinterpret_cast<WCHAR*>(& dFiles[1]), filePath);
  324      GlobalUnlock( hDrop );
  325  
  326      if( OpenClipboard( hwnd ) )
  327      {
  328          EmptyClipboard();
  329          SetClipboardData( CF_HDROP, hDrop );
  330          CloseClipboard();
  331      }
  332  
  333      GlobalFree( hDrop );
  334  
  335      return true;
  336  }
  337  
  338  //----------------------------------------------------------------------
  339  //
  340  // OutputDebug
  341  //
  342  //----------------------------------------------------------------------
  343  void OutputDebug(const TCHAR* format, ...)
  344  {
  345  #if _DEBUG
  346      TCHAR	msg[1024];
  347      va_list	va;
  348  
  349  #ifdef _MSC_VER
  350  // For some reason, ARM64 Debug builds causes an analyzer error on va_start: "error C26492: Don't use const_cast to cast away const or volatile (type.3)."
  351  #pragma warning(push)
  352  #pragma warning(disable : 26492)
  353  #endif
  354      va_start(va, format);
  355  #ifdef _MSC_VER
  356  #pragma warning(pop)
  357  #endif
  358      _vstprintf_s(msg, format, va);
  359      va_end(va);
  360  
  361      OutputDebugString(msg);
  362  #endif
  363  }
  364  
  365  //----------------------------------------------------------------------------
  366  //
  367  // InitializeFonts
  368  //
  369  // Return a bold equivalent of either a DPI aware font face for GUI text or
  370  // just the stock object for DEFAULT_GUI_FONT.
  371  //
  372  //----------------------------------------------------------------------------
  373  void InitializeFonts( HWND hwnd, HFONT *bold )
  374  {
  375      LOGFONT logFont;
  376      bool haveLogFont = false;
  377  
  378      if( *bold )
  379      {
  380          DeleteObject( *bold );
  381          *bold = nullptr;
  382      }
  383  
  384      if( pSystemParametersInfoForDpi && pGetDpiForWindow )
  385      {
  386          NONCLIENTMETRICSW metrics{};
  387          metrics.cbSize = sizeof( metrics );
  388  
  389          if( pSystemParametersInfoForDpi( SPI_GETNONCLIENTMETRICS, sizeof( metrics ), &metrics, 0, pGetDpiForWindow( hwnd ) ) )
  390          {
  391              CopyMemory( &logFont, &metrics.lfMessageFont, sizeof( logFont ) );
  392              haveLogFont = true;
  393          }
  394      }
  395  
  396      if( !haveLogFont )
  397      {
  398          auto normal = static_cast<HFONT>(GetStockObject( DEFAULT_GUI_FONT ));
  399          GetObject( normal, sizeof( logFont ), &logFont );
  400          haveLogFont = true; // for correctness
  401      }
  402  
  403      logFont.lfWeight = FW_BOLD;
  404      *bold = CreateFontIndirect( &logFont );
  405  }
  406  
  407  //----------------------------------------------------------------------------
  408  //
  409  // EnsureForeground
  410  //
  411  //----------------------------------------------------------------------------
  412  void EnsureForeground()
  413  {
  414      if( !IsWindowVisible( g_hWndMain ) )
  415          SetForegroundWindow( g_hWndMain );
  416  }
  417  
  418  //----------------------------------------------------------------------------
  419  //
  420  // RestoreForeground
  421  //
  422  //----------------------------------------------------------------------------
  423  void RestoreForeground()
  424  {
  425      // If the main window is not visible, move foreground to the next window.
  426      if( !IsWindowVisible( g_hWndMain ) ) {
  427  
  428          // Activate the next window by showing and hiding the main window.
  429          MoveWindow( g_hWndMain, 0, 0, 0, 0, FALSE );
  430          ShowWindow( g_hWndMain, SW_SHOWNA );
  431          ShowWindow( g_hWndMain, SW_HIDE );
  432  
  433  		OutputDebug(L"RESTORE FOREGROUND\n");
  434      }
  435  }
  436  
  437  //----------------------------------------------------------------------------
  438  //
  439  // ErrorDialog
  440  //
  441  //----------------------------------------------------------------------------
  442  VOID ErrorDialog( HWND hParent, PCTSTR message, DWORD _Error )
  443  {
  444      LPTSTR	lpMsgBuf;
  445      TCHAR	errmsg[1024];
  446  
  447      FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
  448                      NULL, _Error,
  449                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
  450                      reinterpret_cast<LPTSTR>(&lpMsgBuf), 0, NULL );
  451      _stprintf( errmsg, L"%s: %s", message, lpMsgBuf );
  452  #ifdef __ZOOMIT_POWERTOYS__
  453      if( g_StartedByPowerToys )
  454      {
  455          Logger::error( errmsg );
  456      }
  457  #endif // __ZOOMIT_POWERTOYS__
  458      MessageBox( hParent, errmsg, APPNAME, MB_OK|MB_ICONERROR);
  459  }
  460  
  461  //----------------------------------------------------------------------------
  462  //
  463  // ErrorDialogString
  464  //
  465  //----------------------------------------------------------------------------
  466  VOID ErrorDialogString( HWND hParent, PCTSTR Message, const wchar_t *_Error )
  467  {
  468      TCHAR	errmsg[1024];
  469  
  470      _stprintf_s( errmsg, _countof( errmsg ), L"%s: %s", Message, _Error );
  471      if( hParent == g_hWndMain )
  472      {
  473          EnsureForeground();
  474      }
  475  #ifdef __ZOOMIT_POWERTOYS__
  476      if( g_StartedByPowerToys )
  477      {
  478          Logger::error( errmsg );
  479      }
  480  #endif // __ZOOMIT_POWERTOYS__
  481      MessageBox(hParent, errmsg, APPNAME, MB_OK | MB_ICONERROR);
  482      if( hParent == g_hWndMain )
  483      {
  484          RestoreForeground();
  485      }
  486  }
  487  
  488  
  489  //--------------------------------------------------------------------
  490  //
  491  // SetAutostartFilePath
  492  //
  493  // Sets the file path for later autostart config.
  494  //
  495  //--------------------------------------------------------------------
  496  void SetAutostartFilePath()
  497  {
  498      HKEY hZoomit;
  499      DWORD error;
  500      TCHAR imageFile[MAX_PATH] = { 0 };
  501  
  502      error = RegCreateKeyEx( HKEY_CURRENT_USER, _T( "Software\\Sysinternals\\Zoomit" ), 0,
  503          0, 0, KEY_SET_VALUE, NULL, &hZoomit, NULL );
  504      if( error == ERROR_SUCCESS ) {
  505  
  506          GetModuleFileName( NULL, imageFile + 1, _countof( imageFile ) - 2 );
  507          imageFile[0] = '"';
  508          *(_tcschr( imageFile, 0 )) = '"';
  509          error = RegSetValueEx( hZoomit, L"FilePath", 0, REG_SZ, (BYTE *) imageFile,
  510              static_cast<DWORD>(_tcslen( imageFile ) + 1)* sizeof( TCHAR ));
  511          RegCloseKey( hZoomit );
  512      }
  513  }
  514  
  515  //--------------------------------------------------------------------
  516  //
  517  // ConfigureAutostart
  518  //
  519  // Enables or disables Zoomit autostart for the current image file.
  520  //
  521  //--------------------------------------------------------------------
  522  bool ConfigureAutostart( HWND hParent, bool Enable )
  523  {
  524      HKEY hRunKey, hZoomit;
  525      DWORD error, length, type;
  526      TCHAR imageFile[MAX_PATH];
  527  
  528      error = RegOpenKeyEx( HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
  529          0, KEY_SET_VALUE, &hRunKey );
  530      if( error == ERROR_SUCCESS ) {
  531  
  532          if( Enable ) {
  533  
  534              error = RegOpenKeyEx( HKEY_CURRENT_USER, _T("Software\\Sysinternals\\Zoomit"), 0,
  535                          KEY_QUERY_VALUE, &hZoomit );
  536              if( error == ERROR_SUCCESS ) {
  537  
  538                  length = sizeof(imageFile);
  539  #ifdef _WIN64
  540                  // Unconditionally reset filepath in case this was already set by 32 bit version
  541                  SetAutostartFilePath();
  542  #endif
  543                  error = RegQueryValueEx( hZoomit, _T( "Filepath" ), 0, &type, (BYTE *) imageFile, &length );
  544                  RegCloseKey( hZoomit );
  545                  if( error == ERROR_SUCCESS ) {
  546  
  547                      error = RegSetValueEx( hRunKey, APPNAME, 0, REG_SZ, (BYTE *) imageFile,
  548                          static_cast<DWORD>(_tcslen(imageFile)+1) * sizeof(TCHAR));
  549                  }
  550              }
  551          } else {
  552  
  553              error = RegDeleteValue( hRunKey, APPNAME );
  554              if( error == ERROR_FILE_NOT_FOUND ) error = ERROR_SUCCESS;
  555          }
  556          RegCloseKey( hRunKey );
  557      }
  558      if( error != ERROR_SUCCESS ) {
  559  
  560          ErrorDialog( hParent, L"Error configuring auto start", error );
  561      }
  562      return error == ERROR_SUCCESS;
  563  }
  564  
  565  
  566  //--------------------------------------------------------------------
  567  //
  568  // IsAutostartConfigured
  569  //
  570  // Is this version of zoomit configured to autostart.
  571  //
  572  //--------------------------------------------------------------------
  573  bool IsAutostartConfigured()
  574  {
  575      HKEY	hRunKey;
  576      TCHAR	imageFile[MAX_PATH];
  577      DWORD	error, imageFileLength, type;
  578  
  579      error = RegOpenKeyEx( HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
  580          0, KEY_QUERY_VALUE, &hRunKey );
  581      if( error == ERROR_SUCCESS ) {
  582  
  583          imageFileLength = sizeof(imageFile);
  584          error = RegQueryValueEx( hRunKey, _T("Zoomit"), 0, &type, (BYTE *) imageFile, &imageFileLength );
  585          RegCloseKey( hRunKey );
  586      }
  587      return error == ERROR_SUCCESS;
  588  }
  589  
  590  
  591  #ifndef _WIN64
  592  
  593  //--------------------------------------------------------------------
  594  //
  595  // RunningOnWin64
  596  //
  597  // Returns true if this is the 32-bit version of the executable
  598  // and we're on 64-bit Windows.
  599  //
  600  //--------------------------------------------------------------------
  601  typedef BOOL (__stdcall *P_IS_WOW64PROCESS)(
  602              HANDLE hProcess,
  603              PBOOL Wow64Process
  604              );
  605  BOOL
  606  RunningOnWin64(
  607      VOID
  608      )
  609  {
  610      P_IS_WOW64PROCESS		pIsWow64Process;
  611      BOOL				isWow64 = FALSE;
  612  
  613      pIsWow64Process = (P_IS_WOW64PROCESS) GetProcAddress(GetModuleHandle(_T("kernel32.dll")),
  614                              "IsWow64Process");
  615      if( pIsWow64Process ) {
  616  
  617          pIsWow64Process( GetCurrentProcess(), &isWow64 );
  618      }
  619      return isWow64;
  620  }
  621  
  622  
  623  //--------------------------------------------------------------------
  624  //
  625  // ExtractImageResource
  626  //
  627  // Extracts the specified file that is located in a resource for
  628  // this executable.
  629  //
  630  //--------------------------------------------------------------------
  631  BOOLEAN ExtractImageResource( PTCHAR ResourceName, PTCHAR TargetFile )
  632  {
  633      HRSRC		hResource;
  634      HGLOBAL		hImageResource;
  635      DWORD		dwImageSize;
  636      LPVOID		lpvImage;
  637      FILE		*hFile;
  638  
  639      // Locate the resource
  640      hResource = FindResource( NULL, ResourceName, _T("BINRES") );
  641      if( !hResource )
  642          return FALSE;
  643  
  644      hImageResource	= LoadResource( NULL, hResource );
  645      dwImageSize		= SizeofResource( NULL, hResource );
  646      lpvImage		= LockResource( hImageResource );
  647  
  648      // Now copy it out
  649      _tfopen_s( &hFile, TargetFile, _T("wb") );
  650      if( hFile == NULL ) return FALSE;
  651  
  652      fwrite( lpvImage, 1, dwImageSize, hFile );
  653      fclose( hFile );
  654      return TRUE;
  655  }
  656  
  657  
  658  
  659  //--------------------------------------------------------------------
  660  //
  661  // Run64bitVersion
  662  //
  663  // Returns true if this is the 32-bit version of the executable
  664  // and we're on 64-bit Windows.
  665  //
  666  //--------------------------------------------------------------------
  667  DWORD
  668  Run64bitVersion(
  669      void
  670      )
  671  {
  672      TCHAR		szPath[MAX_PATH];
  673      TCHAR		originalPath[MAX_PATH];
  674      TCHAR		tmpPath[MAX_PATH];
  675      SHELLEXECUTEINFO	info = { 0 };
  676  
  677      if ( GetModuleFileName( NULL, szPath, sizeof(szPath)/sizeof(TCHAR)) == 0 ) {
  678  
  679          return -1;
  680      }
  681      _tcscpy_s( originalPath, _countof(originalPath), szPath );
  682  
  683      *_tcsrchr( originalPath, '.') = 0;
  684      _tcscat_s( originalPath, _countof(szPath), _T("64.exe"));
  685  
  686      //
  687      // Extract the 64-bit version
  688      //
  689      ExpandEnvironmentStrings( L"%TEMP%", tmpPath, sizeof tmpPath / sizeof ( TCHAR));
  690      _tcscat_s( tmpPath, _countof(tmpPath), _tcsrchr( originalPath, '\\'));
  691      _tcscpy_s( szPath, _countof(szPath), tmpPath );
  692      if( !ExtractImageResource( _T("RCZOOMIT64"), szPath )) {
  693  
  694          if( GetFileAttributes( szPath ) == INVALID_FILE_ATTRIBUTES ) {
  695  
  696              ErrorDialog( NULL,_T("Error launching 64-bit version"), GetLastError());
  697              return -1;
  698          }
  699      }
  700  
  701      info.cbSize = sizeof(info);
  702      info.fMask = SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS;
  703      info.lpFile = szPath;
  704      info.lpParameters = GetCommandLine();
  705      info.nShow = SW_SHOWNORMAL;
  706      if( !ShellExecuteEx( &info ) ) {
  707  
  708          ErrorDialog( NULL,_T("Error launching 64-bit version"), GetLastError());
  709          DeleteFile( szPath );
  710          return -1;
  711      }
  712      WaitForSingleObject( info.hProcess, INFINITE );
  713  
  714      DWORD result;
  715      GetExitCodeProcess( info.hProcess, &result );
  716      CloseHandle( info.hProcess );
  717      DeleteFile( szPath );
  718      return result;
  719  }
  720  #endif
  721  
  722  
  723  //----------------------------------------------------------------------------
  724  //
  725  // IsPresentationMode
  726  //
  727  //----------------------------------------------------------------------------
  728  BOOLEAN IsPresentationMode()
  729  {
  730      QUERY_USER_NOTIFICATION_STATE pUserState;
  731  
  732      pSHQueryUserNotificationState( &pUserState );
  733      return pUserState == QUNS_PRESENTATION_MODE;
  734  }
  735  
  736  //----------------------------------------------------------------------------
  737  //
  738  // EnableDisableSecondaryDisplay
  739  //
  740  // Creates a second display on the secondary monitor for displaying the
  741  // break timer.
  742  //
  743  //----------------------------------------------------------------------------
  744  LONG EnableDisableSecondaryDisplay( HWND hWnd, BOOLEAN Enable,
  745                                      PDEVMODE OriginalDevMode )
  746  {
  747      LONG		result;
  748      DEVMODE		devMode{};
  749  
  750      if( Enable ) {
  751  
  752          //
  753          // Prepare the position of Display 2 to be right to the right of Display 1
  754          //
  755          devMode.dmSize = sizeof(devMode);
  756          devMode.dmDriverExtra = 0;
  757          EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devMode);
  758          *OriginalDevMode = devMode;
  759  
  760          //
  761          // Enable display 2 in the registry
  762          //
  763          devMode.dmPosition.x = devMode.dmPelsWidth;
  764          devMode.dmFields = DM_POSITION |
  765                              DM_DISPLAYORIENTATION |
  766                              DM_BITSPERPEL |
  767                              DM_PELSWIDTH |
  768                              DM_PELSHEIGHT |
  769                              DM_DISPLAYFLAGS |
  770                              DM_DISPLAYFREQUENCY;
  771          result = ChangeDisplaySettingsEx( L"\\\\.\\DISPLAY2",
  772                                            &devMode,
  773                                            NULL,
  774                                            CDS_NORESET | CDS_UPDATEREGISTRY,
  775                                            NULL);
  776  
  777      } else {
  778  
  779          OriginalDevMode->dmFields = DM_POSITION |
  780                              DM_DISPLAYORIENTATION |
  781                              DM_BITSPERPEL |
  782                              DM_PELSWIDTH |
  783                              DM_PELSHEIGHT |
  784                              DM_DISPLAYFLAGS |
  785                              DM_DISPLAYFREQUENCY;
  786          result = ChangeDisplaySettingsEx( L"\\\\.\\DISPLAY2",
  787                                            OriginalDevMode,
  788                                            NULL,
  789                                            CDS_NORESET | CDS_UPDATEREGISTRY,
  790                                            NULL);
  791      }
  792  
  793      //
  794      // Update the hardware
  795      //
  796      if( result == DISP_CHANGE_SUCCESSFUL ) {
  797  
  798          if( !ChangeDisplaySettingsEx(NULL, NULL, NULL, 0, NULL)) {
  799  
  800              result = GetLastError();
  801          }
  802  
  803          //
  804          // If enabling, move zoomit to the second monitor
  805          //
  806          if( Enable && result == DISP_CHANGE_SUCCESSFUL ) {
  807  
  808              SetWindowPos(FindWindowW(L"ZoomitClass", NULL),
  809                       NULL,
  810                       devMode.dmPosition.x,
  811                       0,
  812                       0,
  813                       0,
  814                       SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
  815              SetCursorPos( devMode.dmPosition.x+1, devMode.dmPosition.y+1 );
  816          }
  817      }
  818      return result;
  819  }
  820  
  821  //----------------------------------------------------------------------------
  822  //
  823  // GetLineBounds
  824  //
  825  // Gets the rectangle bounding a line, taking into account pen width
  826  //
  827  //----------------------------------------------------------------------------
  828  Gdiplus::Rect GetLineBounds( POINT p1, POINT p2, int penWidth )
  829  {
  830      Gdiplus::Rect rect( min(p1.x, p2.x), min(p1.y, p2.y),
  831                          abs(p1.x - p2.x), abs( p1.y - p2.y));
  832      rect.Inflate( penWidth, penWidth );
  833      return rect;
  834  }
  835  
  836  //----------------------------------------------------------------------------
  837  //
  838  // InvalidateGdiplusRect
  839  //
  840  // Invalidate portion of window specified by Gdiplus::Rect
  841  //
  842  //----------------------------------------------------------------------------
  843  void InvalidateGdiplusRect(HWND hWnd, Gdiplus::Rect BoundsRect)
  844  {
  845      RECT lineBoundsGdi;
  846      lineBoundsGdi.left = BoundsRect.X;
  847      lineBoundsGdi.top = BoundsRect.Y;
  848      lineBoundsGdi.right = BoundsRect.X + BoundsRect.Width;
  849      lineBoundsGdi.bottom = BoundsRect.Y + BoundsRect.Height;
  850      InvalidateRect(hWnd, &lineBoundsGdi, FALSE);
  851  }
  852  
  853  
  854  
  855  //----------------------------------------------------------------------------
  856  //
  857  // CreateGdiplusBitmap
  858  //
  859  // Creates a gdiplus bitmap of the specified region of the HDC.
  860  //
  861  //----------------------------------------------------------------------------
  862  Gdiplus::Bitmap *CreateGdiplusBitmap( HDC hDc, int x, int y, int Width, int Height )
  863  {
  864      HBITMAP hBitmap = CreateCompatibleBitmap(hDc, Width, Height);
  865  
  866      // Create a device context for the new bitmap
  867      HDC hdcNewBitmap = CreateCompatibleDC(hDc);
  868      SelectObject(hdcNewBitmap, hBitmap);
  869  
  870      // Copy from the oldest undo bitmap to the new bitmap using the lineBounds as the source rectangle
  871      BitBlt(hdcNewBitmap, 0, 0, Width, Height, hDc, x, y, SRCCOPY);
  872      Gdiplus::Bitmap *blurBitmap = new Gdiplus::Bitmap(hBitmap, NULL);
  873      DeleteDC(hdcNewBitmap);
  874      DeleteObject(hBitmap);
  875      return blurBitmap;
  876  }
  877  
  878  
  879  //----------------------------------------------------------------------------
  880  //
  881  // CreateBitmapMemoryDIB
  882  //
  883  // Creates a memory DC and DIB for the specified region of the screen.
  884  //
  885  //----------------------------------------------------------------------------
  886  BYTE* CreateBitmapMemoryDIB(HDC hdcScreenCompat, HDC hBitmapDc, Gdiplus::Rect* lineBounds,
  887      HDC* hdcMem, HBITMAP* hDIBOrig, HBITMAP* hPreviousBitmap)
  888  {
  889      // Create a memory DIB for the relevant region of the original bitmap
  890      BITMAPINFO bmiOrig = { 0 };
  891      bmiOrig.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  892      bmiOrig.bmiHeader.biWidth = lineBounds->Width;
  893      bmiOrig.bmiHeader.biHeight = -lineBounds->Height;  // Top-down DIB
  894      bmiOrig.bmiHeader.biPlanes = 1;
  895      bmiOrig.bmiHeader.biBitCount = 32;  // 32 bits per pixel
  896      bmiOrig.bmiHeader.biCompression = BI_RGB;
  897  
  898      VOID* pDIBBitsOrig;
  899      *hDIBOrig = CreateDIBSection(hdcScreenCompat, &bmiOrig, DIB_RGB_COLORS, &pDIBBitsOrig, NULL, 0);
  900  
  901      if( *hDIBOrig == NULL ) {
  902  
  903          OutputDebug(L"NULL DIB: %d\n", GetLastError());
  904          OutputDebug(L"lineBounds: %d %d %d %d\n", lineBounds->X, lineBounds->Y, lineBounds->Width, lineBounds->Height);
  905          return NULL;
  906      }
  907  
  908      *hdcMem = CreateCompatibleDC(hdcScreenCompat);
  909      *hPreviousBitmap = static_cast<HBITMAP>(SelectObject(*hdcMem, *hDIBOrig));
  910  
  911      // Copy the relevant part of hdcScreenCompat to the DIB
  912      BitBlt(*hdcMem, 0, 0, lineBounds->Width, lineBounds->Height, hBitmapDc, lineBounds->X, lineBounds->Y, SRCCOPY);
  913  
  914      // Pointer to the DIB bits
  915      return static_cast<BYTE*>(pDIBBitsOrig);
  916  }
  917  
  918  //----------------------------------------------------------------------------
  919  //
  920  // LockGdiPlusBitmap
  921  //
  922  // Locks the Gdi+ bitmap so that we can access its pixels in memory.
  923  //
  924  //----------------------------------------------------------------------------
  925  #ifdef _MSC_VER
  926      // Analyzers want us to use a scoped object instead of new. But given all the operations done in Bitmaps it seems better to leave it as a heap object.
  927      #pragma warning(push)
  928      #pragma warning(disable : 26402)
  929  #endif
  930  
  931  Gdiplus::BitmapData* LockGdiPlusBitmap(Gdiplus::Bitmap* Bitmap)
  932  {
  933      Gdiplus::BitmapData *lineData = new Gdiplus::BitmapData();
  934      Bitmap->GetPixelFormat();
  935      Gdiplus::Rect lineBitmapBounds(0, 0, Bitmap->GetWidth(), Bitmap->GetHeight());
  936      Bitmap->LockBits(&lineBitmapBounds, Gdiplus::ImageLockModeRead,
  937          Bitmap->GetPixelFormat(), lineData);
  938      return lineData;
  939  }
  940  #ifdef _MSC_VER
  941      #pragma warning(pop)
  942  #endif
  943  
  944  
  945  //----------------------------------------------------------------------------
  946  //
  947  // BlurScreen
  948  //
  949  // Blur the portion of the screen by copying a blurred bitmap with the
  950  // specified shape.
  951  //
  952  //----------------------------------------------------------------------------
  953  void BlurScreen(HDC hdcScreenCompat, Gdiplus::Rect* lineBounds,
  954                      Gdiplus::Bitmap *BlurBitmap, BYTE* pPixels)
  955  {
  956      HDC hdcDIB;
  957      HBITMAP hDibOrigBitmap, hDibBitmap;
  958      BYTE* pDestPixels = CreateBitmapMemoryDIB(hdcScreenCompat, hdcScreenCompat, lineBounds,
  959                                  &hdcDIB, &hDibBitmap, &hDibOrigBitmap);
  960  
  961      // Iterate through the pixels
  962      for (int y = 0; y < lineBounds->Height; ++y) {
  963          for (int x = 0; x < lineBounds->Width; ++x) {
  964              int index = (y * lineBounds->Width * 4) + (x * 4);  // Assuming 4 bytes per pixel
  965              // BYTE b = pPixels[index + 0];  // Blue channel
  966              // BYTE g = pPixels[index + 1];  // Green channel
  967              // BYTE r = pPixels[index + 2];  // Red channel
  968              BYTE a = pPixels[index + 3];  // Alpha channel
  969  
  970              // Check if this is a drawn pixel
  971              if (a != 0) {
  972                  // get the blur pixel
  973                  Gdiplus::Color pixel;
  974                  BlurBitmap->GetPixel(x, y, &pixel);
  975  
  976                  COLORREF newPixel = pixel.GetValue() & 0xFFFFFF;
  977                  pDestPixels[index + 0] = GetRValue(newPixel);
  978                  pDestPixels[index + 1] = GetGValue(newPixel);
  979                  pDestPixels[index + 2] = GetBValue(newPixel);
  980              }
  981          }
  982      }
  983  
  984      // Copy the updated DIB back to hdcScreenCompat
  985      BitBlt(hdcScreenCompat, lineBounds->X, lineBounds->Y, lineBounds->Width, lineBounds->Height, hdcDIB, 0, 0, SRCCOPY);
  986  
  987      // Clean up
  988      SelectObject(hdcDIB, hDibOrigBitmap);
  989      DeleteObject(hDibBitmap);
  990      DeleteDC(hdcDIB);
  991  }
  992  
  993  
  994  
  995  //----------------------------------------------------------------------------
  996  //
  997  // BitmapBlur
  998  //
  999  // Blurs the bitmap.
 1000  //
 1001  //----------------------------------------------------------------------------
 1002  void BitmapBlur(Gdiplus::Bitmap* hBitmap)
 1003  {
 1004      // Git bitmap size
 1005      Gdiplus::Size bitmapSize;
 1006      bitmapSize.Width = hBitmap->GetWidth();
 1007      bitmapSize.Height = hBitmap->GetHeight();
 1008  
 1009      // Blur the new bitmap
 1010      Gdiplus::Blur blurObject;
 1011      Gdiplus::BlurParams blurParams;
 1012      blurParams.radius = g_BlurRadius;
 1013      blurParams.expandEdge = FALSE;
 1014      blurObject.SetParameters(&blurParams);
 1015  
 1016      // Apply blur to image
 1017      RECT linesRect;
 1018      linesRect.left = 0;
 1019      linesRect.top = 0;
 1020      linesRect.right = bitmapSize.Width;
 1021      linesRect.bottom = bitmapSize.Height;
 1022      hBitmap->ApplyEffect(&blurObject, &linesRect);
 1023  }
 1024  
 1025  
 1026  //----------------------------------------------------------------------------
 1027  //
 1028  // DrawBlurredShape
 1029  //
 1030  // Blur a shaped region of the screen.
 1031  //
 1032  //----------------------------------------------------------------------------
 1033  void DrawBlurredShape( DWORD Shape, Gdiplus::Pen *pen, HDC hdcScreenCompat, Gdiplus::Graphics *dstGraphics,
 1034                      int x1, int y1, int x2, int y2)
 1035  {
 1036      // Create a new bitmap that's the size of the area covered by the line + 2 * g_PenWidth
 1037      Gdiplus::Rect lineBounds( min( x1, x2 ), min( y1, y2 ), abs( x2 - x1 ), abs( y2 - y1 ) );
 1038  
 1039      // Expand for line drawing
 1040      if (Shape == DRAW_LINE)
 1041          lineBounds.Inflate( static_cast<int>(g_PenWidth / 2), static_cast<int>(g_PenWidth / 2) );
 1042  
 1043      Gdiplus::Bitmap* lineBitmap = new Gdiplus::Bitmap(lineBounds.Width, lineBounds.Height, PixelFormat32bppARGB);
 1044      Gdiplus::Graphics lineGraphics(lineBitmap);
 1045      static const auto blackBrush = Gdiplus::SolidBrush(Gdiplus::Color::Black);
 1046      switch (Shape) {
 1047      case DRAW_RECTANGLE:
 1048          lineGraphics.FillRectangle(&blackBrush, 0, 0, lineBounds.Width, lineBounds.Height);
 1049          break;
 1050      case DRAW_ELLIPSE:
 1051          lineGraphics.FillEllipse(&blackBrush, 0, 0, lineBounds.Width, lineBounds.Height);
 1052          break;
 1053      case DRAW_LINE:
 1054          OutputDebug(L"BLUR_LINE: %d %d\n", lineBounds.Width, lineBounds.Height);
 1055          lineGraphics.DrawLine( pen, x1 - lineBounds.X, y1 - lineBounds.Y, x2 - lineBounds.X, y2 - lineBounds.Y );
 1056          break;
 1057      }
 1058  
 1059      Gdiplus::BitmapData* lineData = LockGdiPlusBitmap(lineBitmap);
 1060      BYTE* pPixels = static_cast<BYTE*>(lineData->Scan0);
 1061  
 1062      // Create a GDI bitmap that's the size of the lineBounds rectangle
 1063      Gdiplus::Bitmap* blurBitmap = CreateGdiplusBitmap(hdcScreenCompat,
 1064          lineBounds.X, lineBounds.Y, lineBounds.Width, lineBounds.Height);
 1065  
 1066      // Blur it
 1067      BitmapBlur(blurBitmap);
 1068      BlurScreen(hdcScreenCompat, &lineBounds, blurBitmap, pPixels);
 1069  
 1070      // Unlock the bits
 1071      lineBitmap->UnlockBits(lineData);
 1072      delete lineBitmap;
 1073      delete blurBitmap;
 1074  }
 1075  
 1076  //----------------------------------------------------------------------------
 1077  //
 1078  // CreateDrawingBitmap
 1079  //
 1080  // Create a bitmap to draw on.
 1081  //
 1082  //----------------------------------------------------------------------------
 1083  Gdiplus::Bitmap* CreateDrawingBitmap(Gdiplus::Rect lineBounds )
 1084  {
 1085      Gdiplus::Bitmap* lineBitmap = new Gdiplus::Bitmap(lineBounds.Width, lineBounds.Height, PixelFormat32bppARGB);
 1086      Gdiplus::Graphics lineGraphics(lineBitmap);
 1087      return lineBitmap;
 1088  }
 1089  
 1090  
 1091  //----------------------------------------------------------------------------
 1092  //
 1093  // DrawBitmapLine
 1094  //
 1095  // Creates a bitmap and draws a line on it.
 1096  //
 1097  //----------------------------------------------------------------------------
 1098  Gdiplus::Bitmap* DrawBitmapLine(Gdiplus::Rect lineBounds, POINT p1, POINT p2, Gdiplus::Pen *pen)
 1099  {
 1100      Gdiplus::Bitmap* lineBitmap = new Gdiplus::Bitmap(lineBounds.Width, lineBounds.Height, PixelFormat32bppARGB);
 1101      Gdiplus::Graphics lineGraphics(lineBitmap);
 1102  
 1103      // Draw the line on the temporary bitmap
 1104      lineGraphics.DrawLine(pen, static_cast<INT>(p1.x - lineBounds.X), static_cast<INT>(p1.y - lineBounds.Y),
 1105          static_cast<INT>(p2.x - lineBounds.X), static_cast<INT>(p2.y - lineBounds.Y));
 1106  
 1107      return lineBitmap;
 1108  }
 1109  
 1110  
 1111  //----------------------------------------------------------------------------
 1112  //
 1113  // ColorFromColorRef
 1114  //
 1115  // Returns a color object from the colorRef that includes the alpha channel
 1116  //
 1117  //----------------------------------------------------------------------------
 1118  Gdiplus::Color ColorFromColorRef(DWORD colorRef) {
 1119      BYTE a = (colorRef >> 24) & 0xFF;  // Extract the alpha channel value
 1120      BYTE b = (colorRef >> 16) & 0xFF;  // Extract the red channel value
 1121      BYTE g = (colorRef >> 8) & 0xFF;   // Extract the green channel value
 1122      BYTE r = colorRef & 0xFF;          // Extract the blue channel value
 1123      OutputDebug( L"ColorFromColorRef: %d %d %d %d\n", a, r, g, b );
 1124      return Gdiplus::Color(a, r, g, b);
 1125  }
 1126  
 1127  //----------------------------------------------------------------------------
 1128  //
 1129  // AdjustHighlighterColor
 1130  //
 1131  // Lighten the color.
 1132  //
 1133  //----------------------------------------------------------------------------
 1134  void AdjustHighlighterColor(BYTE* red, BYTE* green, BYTE* blue) {
 1135  
 1136      // Adjust the color to be more visible
 1137      *red = min( 0xFF, *red ? *red + 0x40 : *red + 0x80 );
 1138      *green = min( 0xFF, *green ? *green + 0x40 : *green + 0x80);
 1139      *blue = min( 0xFF, *blue ? *blue + 0x40 : *blue + 0x80);
 1140  }
 1141  
 1142  //----------------------------------------------------------------------------
 1143  //
 1144  // BlendColors
 1145  //
 1146  // Blends two colors together using the alpha channel of the second color.
 1147  // The highlighter is the second color.
 1148  //
 1149  //----------------------------------------------------------------------------
 1150  COLORREF BlendColors(COLORREF color1, const Gdiplus::Color& color2) {
 1151  
 1152      BYTE redResult, greenResult, blueResult;
 1153  
 1154      // Extract the channels from the COLORREF
 1155      BYTE red1 = GetRValue(color1);
 1156      BYTE green1 = GetGValue(color1);
 1157      BYTE blue1 = GetBValue(color1);
 1158  
 1159      // Get the channels and alpha from the Gdiplus::Color
 1160      BYTE blue2 = color2.GetRed();
 1161      BYTE green2 = color2.GetGreen();
 1162      BYTE red2 = color2.GetBlue();
 1163      float alpha2 = color2.GetAlpha() / 255.0f;  // Normalize to [0, 1]
 1164      //alpha2 /= 2; // Use half the alpha for higher contrast
 1165  
 1166      // Don't blend grey's as much
 1167      // int minValue = min(red1, min(green1, blue1));
 1168      // int maxValue = max(red1, max(green1, blue1));
 1169      if(TRUE) { // red1 > 0x10 && red1 < 0xC0 && (maxValue - minValue < 0x40)) {
 1170  
 1171          // This does a standard bright highlight
 1172          alpha2 = 0;
 1173          AdjustHighlighterColor( &red2, &green2, &blue2 );
 1174          redResult	= red2 & red1;
 1175          greenResult = green2 & green1;
 1176          blueResult	= blue2 & blue1;
 1177      }
 1178      else {
 1179  
 1180          // Blend each channel
 1181          redResult = static_cast<BYTE>(red2 * alpha2 + red1 * (1 - alpha2));
 1182          greenResult = static_cast<BYTE>(green2 * alpha2 + green1 * (1 - alpha2));
 1183          blueResult = static_cast<BYTE>(blue2 * alpha2 + blue1 * (1 - alpha2));
 1184      }
 1185      // Combine the result channels back into a COLORREF
 1186      return RGB(redResult, greenResult, blueResult);
 1187  }
 1188  
 1189  
 1190  
 1191  //----------------------------------------------------------------------------
 1192  //
 1193  // DrawHighlightedShape
 1194  //
 1195  // Draws the shape with the highlighter color.
 1196  //
 1197  //----------------------------------------------------------------------------
 1198  void DrawHighlightedShape( DWORD Shape, HDC hdcScreenCompat, Gdiplus::Brush *pBrush,
 1199                          Gdiplus::Pen *pPen, int x1, int y1, int x2, int y2)
 1200  {
 1201      // Create a new bitmap that's the size of the area covered by the line + 2 * g_PenWidth
 1202      Gdiplus::Rect lineBounds(min(x1, x2), min(y1, y2), abs(x2 - x1), abs(y2 - y1));
 1203  
 1204      OutputDebug(L"DrawHighlightedShape\n");
 1205  
 1206      // Expand for line drawing
 1207      if (Shape == DRAW_LINE)
 1208          lineBounds.Inflate(static_cast<int>(g_PenWidth / 2), static_cast<int>(g_PenWidth / 2));
 1209  
 1210      Gdiplus::Bitmap* lineBitmap = CreateDrawingBitmap(lineBounds);
 1211      Gdiplus::Graphics lineGraphics(lineBitmap);
 1212      switch (Shape) {
 1213      case DRAW_RECTANGLE:
 1214          lineGraphics.FillRectangle(pBrush, 0, 0, lineBounds.Width, lineBounds.Height);
 1215          break;
 1216      case DRAW_ELLIPSE:
 1217          lineGraphics.FillEllipse( pBrush, 0, 0, lineBounds.Width, lineBounds.Height);
 1218          break;
 1219      case DRAW_LINE:
 1220          lineGraphics.DrawLine(pPen, x1 - lineBounds.X, y1 - lineBounds.Y, x2 - lineBounds.X, y2 - lineBounds.Y);
 1221          break;
 1222      }
 1223  
 1224      Gdiplus::BitmapData* lineData = LockGdiPlusBitmap(lineBitmap);
 1225      BYTE* pPixels = static_cast<BYTE*>(lineData->Scan0);
 1226  
 1227      // Create a DIB section for efficient pixel manipulation
 1228      BITMAPINFO bmi = { 0 };
 1229      bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
 1230      bmi.bmiHeader.biWidth = lineBounds.Width;
 1231      bmi.bmiHeader.biHeight = -lineBounds.Height;  // Top-down DIB
 1232      bmi.bmiHeader.biPlanes = 1;
 1233      bmi.bmiHeader.biBitCount = 32;  // 32 bits per pixel
 1234      bmi.bmiHeader.biCompression = BI_RGB;
 1235  
 1236      VOID* pDIBBits;
 1237      HBITMAP hDIB = CreateDIBSection(hdcScreenCompat, &bmi, DIB_RGB_COLORS, &pDIBBits, NULL, 0);
 1238  
 1239      HDC hdcDIB = CreateCompatibleDC(hdcScreenCompat);
 1240      SelectObject(hdcDIB, hDIB);
 1241  
 1242      // Copy the relevant part of hdcScreenCompat to the DIB
 1243      BitBlt(hdcDIB, 0, 0, lineBounds.Width, lineBounds.Height, hdcScreenCompat, lineBounds.X, lineBounds.Y, SRCCOPY);
 1244  
 1245      // Pointer to the DIB bits
 1246      BYTE* pDestPixels = static_cast<BYTE*>(pDIBBits);
 1247  
 1248      // Pointer to screen bits
 1249      HDC hdcDIBOrig;
 1250      HBITMAP hDibOrigBitmap, hDibBitmap;
 1251      BYTE* pDestPixels2 = CreateBitmapMemoryDIB(hdcScreenCompat, hdcScreenCompat, &lineBounds,
 1252          &hdcDIBOrig, &hDibBitmap, &hDibOrigBitmap);
 1253  
 1254      for (int y = 0; y < lineBounds.Height; ++y) {
 1255          for (int x = 0; x < lineBounds.Width; ++x) {
 1256              int index = (y * lineBounds.Width * 4) + (x * 4);  // Assuming 4 bytes per pixel
 1257              // BYTE b = pPixels[index + 0];  // Blue channel
 1258              // BYTE g = pPixels[index + 1];  // Green channel
 1259              // BYTE r = pPixels[index + 2];  // Red channel
 1260              BYTE a = pPixels[index + 3];  // Alpha channel
 1261  
 1262              // Check if this is a drawn pixel
 1263              if (a != 0) {
 1264                  // Assuming pDestPixels is a valid pointer to the destination bitmap's pixel data
 1265                  BYTE destB = pDestPixels2[index + 0];  // Blue channel
 1266                  BYTE destG = pDestPixels2[index + 1];  // Green channel
 1267                  BYTE destR = pDestPixels2[index + 2];  // Red channel
 1268  
 1269                  // Create a COLORREF value from the destination pixel data
 1270                  COLORREF currentPixel = RGB(destR, destG, destB);
 1271                  // Blend the colors
 1272                  COLORREF newPixel = BlendColors(currentPixel, g_PenColor);
 1273                  // Update the destination pixel data with the new color
 1274                  pDestPixels[index + 0] = GetBValue(newPixel);
 1275                  pDestPixels[index + 1] = GetGValue(newPixel);
 1276                  pDestPixels[index + 2] = GetRValue(newPixel);
 1277              }
 1278          }
 1279      }
 1280  
 1281      // Copy the updated DIB back to hdcScreenCompat
 1282      BitBlt(hdcScreenCompat, lineBounds.X, lineBounds.Y, lineBounds.Width, lineBounds.Height, hdcDIB, 0, 0, SRCCOPY);
 1283  
 1284      // Clean up
 1285      DeleteObject(hDIB);
 1286      DeleteDC(hdcDIB);
 1287  
 1288      SelectObject(hdcDIBOrig, hDibOrigBitmap);
 1289      DeleteObject(hDibBitmap);
 1290      DeleteDC(hdcDIBOrig);
 1291  
 1292      // Invalidate the updated rectangle
 1293      //InvalidateGdiplusRect(hWnd, lineBounds);
 1294  }
 1295  
 1296  //----------------------------------------------------------------------------
 1297  //
 1298  // CreateFadedDesktopBackground
 1299  //
 1300  // Creates a snapshot of the desktop that's faded and alpha blended with
 1301  // black.
 1302  //
 1303  //----------------------------------------------------------------------------
 1304  HBITMAP CreateFadedDesktopBackground( HDC hdc, LPRECT rcScreen, LPRECT rcCrop )
 1305  {
 1306      // create bitmap
 1307      int		width		= rcScreen->right - rcScreen->left;
 1308      int		height		= rcScreen->bottom - rcScreen->top;
 1309      HDC		hdcScreen	= hdc;
 1310      HDC		hdcMem		= CreateCompatibleDC( hdcScreen );
 1311      HBITMAP	hBitmap		= CreateCompatibleBitmap( hdcScreen, width, height );
 1312      HBITMAP	hOld		= static_cast<HBITMAP>(SelectObject( hdcMem, hBitmap ));
 1313      HBRUSH	hBrush		= CreateSolidBrush(RGB(0, 0, 0));
 1314  
 1315      // start with black background
 1316      FillRect( hdcMem, rcScreen, hBrush );
 1317      if(rcCrop != NULL && rcCrop->left != -1 ) {
 1318  
 1319          // copy screen contents that are not cropped
 1320          BitBlt(hdcMem, rcCrop->left, rcCrop->top, rcCrop->right - rcCrop->left,
 1321              rcCrop->bottom - rcCrop->top, hdcScreen, rcCrop->left, rcCrop->top, SRCCOPY);
 1322      }
 1323  
 1324      // blend screen contents into it
 1325      BLENDFUNCTION	blend = { 0 };
 1326      blend.BlendOp				= AC_SRC_OVER;
 1327      blend.BlendFlags			= 0;
 1328      blend.SourceConstantAlpha   = 0x4F;
 1329      blend.AlphaFormat			= 0;
 1330      AlphaBlend( hdcMem,0, 0, width, height,
 1331                  hdcScreen, rcScreen->left, rcScreen->top,
 1332                  width, height, blend );
 1333  
 1334      SelectObject( hdcMem, hOld );
 1335      DeleteDC( hdcMem );
 1336      DeleteObject(hBrush);
 1337      ReleaseDC( NULL, hdcScreen );
 1338  
 1339      return hBitmap;
 1340  }
 1341  
 1342  //----------------------------------------------------------------------------
 1343  //
 1344  // AdjustToMoveBoundary
 1345  //
 1346  // Shifts to accomodate move boundary.
 1347  //
 1348  //----------------------------------------------------------------------------
 1349  void AdjustToMoveBoundary( float zoomLevel, int *coordinate, int cursor, int size, int max )
 1350  {
 1351      int diff = static_cast<int> (static_cast<float>(size)/ static_cast<float>(LIVEZOOM_MOVE_REGIONS));
 1352      if( cursor - *coordinate < diff )
 1353          *coordinate = max( 0, cursor - diff );
 1354      else if( (*coordinate + size) - cursor < diff )
 1355          *coordinate = min( cursor + diff - size, max - size );
 1356  }
 1357  
 1358  //----------------------------------------------------------------------------
 1359  //
 1360  // GetZoomedTopLeftCoordinates
 1361  //
 1362  // Gets the left top coordinate of the zoomed area of the screen
 1363  //
 1364  //----------------------------------------------------------------------------
 1365  void GetZoomedTopLeftCoordinates( float zoomLevel, POINT *cursorPos, int *x, int width, int *y, int height )
 1366  {
 1367      // smoother and more natural zoom in
 1368      float scaledWidth = width/zoomLevel;
 1369      float scaledHeight = height/zoomLevel;
 1370      *x = max( 0, min( (int) (width - scaledWidth), (int) (cursorPos->x - (int) (((float) cursorPos->x/ (float) width)*scaledWidth))));
 1371      AdjustToMoveBoundary( zoomLevel, x, cursorPos->x, static_cast<int>(scaledWidth), width );
 1372      *y = max( 0, min( (int) (height - scaledHeight), (int) (cursorPos->y - (int) (((float) cursorPos->y/ (float) height)*scaledHeight))));
 1373      AdjustToMoveBoundary( zoomLevel, y, cursorPos->y, static_cast<int>(scaledHeight), height );
 1374  }
 1375  
 1376  
 1377  //----------------------------------------------------------------------------
 1378  //
 1379  // ScaleImage
 1380  //
 1381  // Use gdi+ for anti-aliased bitmap stretching.
 1382  //
 1383  //----------------------------------------------------------------------------
 1384  void ScaleImage( HDC hdcDst, float xDst, float yDst, float wDst, float hDst,
 1385                   HBITMAP bmSrc, float xSrc, float ySrc, float wSrc, float hSrc )
 1386  {
 1387      Gdiplus::Graphics	dstGraphics( hdcDst );
 1388      {
 1389          Gdiplus::Bitmap		srcBitmap( bmSrc, NULL );
 1390  
 1391          // Use high quality interpolation when smooth image is enabled
 1392          if (g_SmoothImage) {
 1393              dstGraphics.SetInterpolationMode( Gdiplus::InterpolationModeHighQuality );
 1394          } else {
 1395              dstGraphics.SetInterpolationMode( Gdiplus::InterpolationModeLowQuality );
 1396          }
 1397          dstGraphics.SetPixelOffsetMode( Gdiplus::PixelOffsetModeHalf );
 1398  
 1399          dstGraphics.DrawImage( &srcBitmap, Gdiplus::RectF(xDst,yDst,wDst,hDst), xSrc, ySrc, wSrc, hSrc, Gdiplus::UnitPixel );
 1400      }
 1401  }
 1402  
 1403  
 1404  //----------------------------------------------------------------------------
 1405  //
 1406  // GetEncoderClsid
 1407  //
 1408  //----------------------------------------------------------------------------
 1409  int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
 1410  {
 1411     UINT  num = 0;          // number of image encoders
 1412     UINT  size = 0;         // size of the image encoder array in bytes
 1413  using namespace Gdiplus;
 1414  
 1415     ImageCodecInfo* pImageCodecInfo = NULL;
 1416  
 1417     GetImageEncodersSize(&num, &size);
 1418     if(size == 0)
 1419        return -1;  // Failure
 1420  
 1421     pImageCodecInfo = static_cast<ImageCodecInfo*>(malloc(size));
 1422     if(pImageCodecInfo == NULL)
 1423        return -1;  // Failure
 1424  
 1425     GetImageEncoders(num, size, pImageCodecInfo);
 1426  
 1427     for(UINT j = 0; j < num; ++j)
 1428     {
 1429        if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
 1430        {
 1431           *pClsid = pImageCodecInfo[j].Clsid;
 1432           free(pImageCodecInfo);
 1433           return j;  // Success
 1434        }
 1435     }
 1436  
 1437     free(pImageCodecInfo);
 1438     return -1;  // Failure
 1439  }
 1440  
 1441  //----------------------------------------------------------------------
 1442  //
 1443  // ConvertToUnicode
 1444  //
 1445  //----------------------------------------------------------------------
 1446  void
 1447  ConvertToUnicode(
 1448      PCHAR aString,
 1449      PWCHAR  wString,
 1450      DWORD wStringLength
 1451      )
 1452  {
 1453      size_t	len;
 1454  
 1455      len = MultiByteToWideChar( CP_ACP, 0, aString, static_cast<int>(strlen(aString)),
 1456                  wString, wStringLength );
 1457      wString[len] = 0;
 1458  }
 1459  
 1460  
 1461  //----------------------------------------------------------------------------
 1462  //
 1463  // LoadImageFile
 1464  //
 1465  // Use gdi+ to load an image.
 1466  //
 1467  //----------------------------------------------------------------------------
 1468  HBITMAP LoadImageFile( PTCHAR Filename )
 1469  {
 1470      HBITMAP		hBmp;
 1471  
 1472      Gdiplus::Bitmap		*bitmap;
 1473  
 1474      bitmap = Gdiplus::Bitmap::FromFile(Filename);
 1475      if( bitmap->GetHBITMAP( NULL, &hBmp )) {
 1476  
 1477          return NULL;
 1478      }
 1479      delete bitmap;
 1480      return hBmp;
 1481  }
 1482  
 1483  
 1484  //----------------------------------------------------------------------------
 1485  //
 1486  // SavePng
 1487  //
 1488  // Use gdi+ to save a PNG.
 1489  //
 1490  //----------------------------------------------------------------------------
 1491  DWORD SavePng( LPCTSTR Filename, HBITMAP hBitmap )
 1492  {
 1493      Gdiplus::Bitmap		bitmap( hBitmap, NULL );
 1494      CLSID pngClsid;
 1495      GetEncoderClsid(L"image/png", &pngClsid);
 1496      if( bitmap.Save( Filename, &pngClsid, NULL )) {
 1497  
 1498          return GetLastError();
 1499      }
 1500      return ERROR_SUCCESS;
 1501  }
 1502  
 1503  
 1504  //----------------------------------------------------------------------------
 1505  //
 1506  // EnableDisableTrayIcon
 1507  //
 1508  //----------------------------------------------------------------------------
 1509  void EnableDisableTrayIcon( HWND hWnd, BOOLEAN Enable )
 1510  {
 1511      NOTIFYICONDATA tNotifyIconData;
 1512  
 1513      memset( &tNotifyIconData, 0, sizeof(tNotifyIconData));
 1514      tNotifyIconData.cbSize = sizeof(NOTIFYICONDATA);
 1515      tNotifyIconData.hWnd = hWnd;
 1516      tNotifyIconData.uID = 1;
 1517      tNotifyIconData.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
 1518      tNotifyIconData.uCallbackMessage = WM_USER_TRAY_ACTIVATE;
 1519      tNotifyIconData.hIcon = LoadIcon( g_hInstance, L"APPICON" );
 1520      lstrcpyn(tNotifyIconData.szTip, APPNAME, sizeof(APPNAME));
 1521      Shell_NotifyIcon(Enable ? NIM_ADD : NIM_DELETE, &tNotifyIconData);
 1522  }
 1523  
 1524  //----------------------------------------------------------------------------
 1525  //
 1526  // EnableDisableOpacity
 1527  //
 1528  //----------------------------------------------------------------------------
 1529  void EnableDisableOpacity( HWND hWnd, BOOLEAN Enable )
 1530  {
 1531      DWORD	exStyle;
 1532  
 1533      if( pSetLayeredWindowAttributes && g_BreakOpacity != 100 ) {
 1534  
 1535          if( Enable ) {
 1536  
 1537              exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
 1538              SetWindowLong(hWnd, GWL_EXSTYLE, (exStyle | WS_EX_LAYERED));
 1539  
 1540              pSetLayeredWindowAttributes(hWnd, 0, static_cast<BYTE> ((255 * g_BreakOpacity) / 100), LWA_ALPHA);
 1541              RedrawWindow(hWnd, 0, 0, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
 1542  
 1543          } else {
 1544  
 1545              exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
 1546              SetWindowLong(hWnd, GWL_EXSTYLE, (exStyle & ~WS_EX_LAYERED));
 1547          }
 1548      }
 1549  }
 1550  
 1551  //----------------------------------------------------------------------------
 1552  //
 1553  // EnableDisableScreenSaver
 1554  //
 1555  //----------------------------------------------------------------------------
 1556  void EnableDisableScreenSaver( BOOLEAN Enable )
 1557  {
 1558      SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,Enable,0,0);
 1559      SystemParametersInfo(SPI_SETPOWEROFFACTIVE,Enable,0,0);
 1560      SystemParametersInfo(SPI_SETLOWPOWERACTIVE,Enable,0,0);
 1561  }
 1562  
 1563  //----------------------------------------------------------------------------
 1564  //
 1565  // EnableDisableStickyKeys
 1566  //
 1567  //----------------------------------------------------------------------------
 1568  void EnableDisableStickyKeys( BOOLEAN Enable )
 1569  {
 1570      static STICKYKEYS	prevStickyKeyValue = {0};
 1571      STICKYKEYS			newStickyKeyValue = {0};
 1572  
 1573      // Need to do this on Vista tablet to stop sticky key popup when you
 1574      // hold down the shift key and draw with the pen.
 1575      if( Enable ) {
 1576  
 1577          if( prevStickyKeyValue.cbSize == sizeof(STICKYKEYS)) {
 1578  
 1579              SystemParametersInfo(SPI_SETSTICKYKEYS,
 1580                      sizeof(STICKYKEYS), &prevStickyKeyValue, SPIF_SENDCHANGE);
 1581          }
 1582  
 1583      } else {
 1584  
 1585          prevStickyKeyValue.cbSize = sizeof(STICKYKEYS);
 1586          if (SystemParametersInfo(SPI_GETSTICKYKEYS, sizeof(STICKYKEYS),
 1587                  &prevStickyKeyValue, 0)) {
 1588  
 1589              newStickyKeyValue.cbSize = sizeof(STICKYKEYS);
 1590              newStickyKeyValue.dwFlags = 0;
 1591              if( !SystemParametersInfo(SPI_SETSTICKYKEYS,
 1592                  sizeof(STICKYKEYS), &newStickyKeyValue, SPIF_SENDCHANGE)) {
 1593  
 1594                  // DWORD error = GetLastError();
 1595  
 1596              }
 1597          }
 1598      }
 1599  }
 1600  
 1601  
 1602  //----------------------------------------------------------------------------
 1603  //
 1604  // GetKeyMod
 1605  //
 1606  //----------------------------------------------------------------------------
 1607  constexpr DWORD GetKeyMod( DWORD Key )
 1608  {
 1609      DWORD	 keyMod = 0;
 1610      if( (Key >> 8) & HOTKEYF_ALT ) keyMod |= MOD_ALT;
 1611      if( (Key >> 8) & HOTKEYF_CONTROL) keyMod |= MOD_CONTROL;
 1612      if( (Key >> 8) & HOTKEYF_SHIFT) keyMod |= MOD_SHIFT;
 1613      if( (Key >> 8) & HOTKEYF_EXT) keyMod |= MOD_WIN;
 1614      return keyMod;
 1615  }
 1616  
 1617  
 1618  //----------------------------------------------------------------------------
 1619  //
 1620  // AdvancedBreakProc
 1621  //
 1622  //----------------------------------------------------------------------------
 1623  INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
 1624  {
 1625      TCHAR	opacity[10];
 1626      static	TCHAR newSoundFile[MAX_PATH];
 1627      static	TCHAR newBackgroundFile[MAX_PATH];
 1628      TCHAR	filePath[MAX_PATH], initDir[MAX_PATH];
 1629      DWORD	i;
 1630      static UINT currentDpi = DPI_BASELINE;
 1631  
 1632      switch ( message )  {
 1633      case WM_INITDIALOG:
 1634          // Set the dialog icon
 1635          {
 1636              HICON hIcon = LoadIcon( g_hInstance, L"APPICON" );
 1637              if( hIcon )
 1638              {
 1639                  SendMessage( hDlg, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIcon) );
 1640                  SendMessage( hDlg, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(hIcon) );
 1641              }
 1642          }
 1643          if( pSHAutoComplete ) {
 1644              pSHAutoComplete( GetDlgItem( hDlg, IDC_SOUND_FILE), SHACF_FILESYSTEM );
 1645              pSHAutoComplete( GetDlgItem( hDlg, IDC_BACKGROUND_FILE), SHACF_FILESYSTEM );
 1646          }
 1647          CheckDlgButton( hDlg, IDC_CHECK_BACKGROUND_FILE,
 1648              g_BreakShowBackgroundFile ? BST_CHECKED: BST_UNCHECKED );
 1649          CheckDlgButton( hDlg, IDC_CHECK_SOUND_FILE,
 1650              g_BreakPlaySoundFile ? BST_CHECKED: BST_UNCHECKED );
 1651          CheckDlgButton( hDlg, IDC_CHECK_SHOW_EXPIRED,
 1652              g_ShowExpiredTime ? BST_CHECKED : BST_UNCHECKED );
 1653          CheckDlgButton( hDlg, IDC_CHECK_BACKGROUND_STRETCH,
 1654              g_BreakBackgroundStretch ? BST_CHECKED : BST_UNCHECKED );
 1655  #if 0
 1656          CheckDlgButton( hDlg, IDC_CHECK_SECONDARYDISPLAY,
 1657              g_BreakOnSecondary ? BST_CHECKED : BST_UNCHECKED );
 1658  #endif
 1659          if( pSetLayeredWindowAttributes == NULL ) {
 1660  
 1661              EnableWindow( GetDlgItem( hDlg, IDC_OPACITY ), FALSE );
 1662          }
 1663  
 1664          // sound file
 1665          if( !g_BreakPlaySoundFile ) {
 1666  
 1667              EnableWindow( GetDlgItem( hDlg, IDC_STATIC_SOUND_FILE ), FALSE );
 1668              EnableWindow( GetDlgItem( hDlg, IDC_SOUND_FILE ), FALSE );
 1669              EnableWindow( GetDlgItem( hDlg, IDC_SOUND_BROWSE ), FALSE );
 1670          }
 1671          _tcscpy( newSoundFile, g_BreakSoundFile );
 1672          _tcscpy( filePath, g_BreakSoundFile );
 1673          if( _tcsrchr( filePath, '\\' )) _tcscpy( filePath, _tcsrchr( g_BreakSoundFile, '\\' )+1);
 1674          if( _tcsrchr( filePath, '.' )) *_tcsrchr( filePath, '.' ) = 0;
 1675          SetDlgItemText( hDlg, IDC_SOUND_FILE, filePath );
 1676  
 1677          // background file
 1678          if( !g_BreakShowBackgroundFile ) {
 1679  
 1680              EnableWindow( GetDlgItem( hDlg, IDC_STATIC_DESKTOP_BACKGROUND ), FALSE );
 1681              EnableWindow( GetDlgItem( hDlg, IDC_STATIC_DESKTOP_BACKGROUND ), FALSE );
 1682              EnableWindow( GetDlgItem( hDlg, IDC_STATIC_BACKGROUND_FILE ), FALSE );
 1683              EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_FILE ), FALSE );
 1684              EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_BROWSE ), FALSE );
 1685              EnableWindow( GetDlgItem( hDlg, IDC_CHECK_BACKGROUND_STRETCH ), FALSE );
 1686          }
 1687          CheckDlgButton( hDlg,
 1688              g_BreakShowDesktop ? IDC_STATIC_DESKTOP_BACKGROUND : IDC_STATIC_BACKGROUND_FILE, BST_CHECKED );
 1689          _tcscpy( newBackgroundFile, g_BreakBackgroundFile );
 1690          SetDlgItemText( hDlg, IDC_BACKGROUND_FILE, g_BreakBackgroundFile );
 1691  
 1692          CheckDlgButton( hDlg, IDC_TIMER_POS1 + g_BreakTimerPosition, BST_CHECKED );
 1693  
 1694          for( i = 10; i <= 100; i += 10) {
 1695  
 1696              _stprintf( opacity, L"%d%%", i );
 1697              SendMessage( GetDlgItem( hDlg, IDC_OPACITY ), CB_ADDSTRING, 0,
 1698                      reinterpret_cast<LPARAM>(opacity));
 1699          }
 1700          SendMessage( GetDlgItem( hDlg, IDC_OPACITY ), CB_SETCURSEL,
 1701                  g_BreakOpacity / 10 - 1, 0 );
 1702  
 1703          // Apply DPI scaling to the dialog
 1704          currentDpi = GetDpiForWindowHelper( hDlg );
 1705          if( currentDpi != DPI_BASELINE )
 1706          {
 1707              ScaleDialogForDpi( hDlg, currentDpi, DPI_BASELINE );
 1708          }
 1709  
 1710          // Apply dark mode
 1711          ApplyDarkModeToDialog( hDlg );
 1712          return TRUE;
 1713  
 1714      case WM_DPICHANGED:
 1715          HandleDialogDpiChange( hDlg, wParam, lParam, currentDpi );
 1716          return TRUE;
 1717  
 1718      case WM_ERASEBKGND:
 1719          if (IsDarkModeEnabled())
 1720          {
 1721              HDC hdc = reinterpret_cast<HDC>(wParam);
 1722              RECT rc;
 1723              GetClientRect(hDlg, &rc);
 1724              FillRect(hdc, &rc, GetDarkModeBrush());
 1725              return TRUE;
 1726          }
 1727          break;
 1728  
 1729      case WM_CTLCOLORDLG:
 1730      case WM_CTLCOLORSTATIC:
 1731      case WM_CTLCOLORBTN:
 1732      case WM_CTLCOLOREDIT:
 1733      case WM_CTLCOLORLISTBOX:
 1734      {
 1735          HDC hdc = reinterpret_cast<HDC>(wParam);
 1736          HWND hCtrl = reinterpret_cast<HWND>(lParam);
 1737          HBRUSH hBrush = HandleDarkModeCtlColor(hdc, hCtrl, message);
 1738          if (hBrush)
 1739          {
 1740              return reinterpret_cast<INT_PTR>(hBrush);
 1741          }
 1742          break;
 1743      }
 1744  
 1745      case WM_COMMAND:
 1746          switch ( HIWORD( wParam )) {
 1747          case BN_CLICKED:
 1748              if( LOWORD( wParam ) == IDC_CHECK_SOUND_FILE ) {
 1749  
 1750                  EnableWindow( GetDlgItem( hDlg, IDC_STATIC_SOUND_FILE ),
 1751                          IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED );
 1752                  EnableWindow( GetDlgItem( hDlg, IDC_SOUND_FILE ),
 1753                          IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED );
 1754                  EnableWindow( GetDlgItem( hDlg, IDC_SOUND_BROWSE ),
 1755                          IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED );
 1756              }
 1757              if( LOWORD( wParam ) == IDC_CHECK_BACKGROUND_FILE ) {
 1758  
 1759                  EnableWindow( GetDlgItem( hDlg, IDC_CHECK_BACKGROUND_STRETCH ),
 1760                          IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
 1761                  EnableWindow( GetDlgItem( hDlg, IDC_STATIC_DESKTOP_BACKGROUND ),
 1762                          IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
 1763                  EnableWindow( GetDlgItem( hDlg, IDC_STATIC_BACKGROUND_FILE ),
 1764                          IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
 1765                  EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_FILE ),
 1766                          IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
 1767                  EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_BROWSE ),
 1768                          IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
 1769              }
 1770              break;
 1771          }
 1772          switch ( LOWORD( wParam )) {
 1773          case IDC_SOUND_BROWSE:
 1774          {
 1775              auto openDialog = wil::CoCreateInstance<IFileOpenDialog>( CLSID_FileOpenDialog );
 1776  
 1777              FILEOPENDIALOGOPTIONS options;
 1778              if( SUCCEEDED( openDialog->GetOptions( &options ) ) )
 1779                  openDialog->SetOptions( options | FOS_FORCEFILESYSTEM );
 1780  
 1781              COMDLG_FILTERSPEC fileTypes[] = {
 1782                  { L"Sounds", L"*.wav" },
 1783                  { L"All Files", L"*.*" }
 1784              };
 1785              openDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
 1786              openDialog->SetFileTypeIndex( 1 );
 1787              openDialog->SetDefaultExtension( L"wav" );
 1788              openDialog->SetTitle( L"ZoomIt: Specify Sound File..." );
 1789  
 1790              // Set initial folder
 1791              GetDlgItemText( hDlg, IDC_SOUND_FILE, filePath, _countof( filePath ) );
 1792              if( _tcsrchr( filePath, '\\' ) )
 1793              {
 1794                  _tcscpy( initDir, filePath );
 1795                  *( _tcsrchr( initDir, '\\' ) + 1 ) = 0;
 1796              }
 1797              else
 1798              {
 1799                  _tcscpy( filePath, L"%WINDIR%\\Media" );
 1800                  ExpandEnvironmentStrings( filePath, initDir, _countof( initDir ) );
 1801              }
 1802              wil::com_ptr<IShellItem> folderItem;
 1803              if( SUCCEEDED( SHCreateItemFromParsingName( initDir, nullptr, IID_PPV_ARGS( &folderItem ) ) ) )
 1804              {
 1805                  openDialog->SetFolder( folderItem.get() );
 1806              }
 1807  
 1808              OpenSaveDialogEvents* pEvents = new OpenSaveDialogEvents(false);
 1809              DWORD dwCookie = 0;
 1810              openDialog->Advise( pEvents, &dwCookie );
 1811  
 1812              if( SUCCEEDED( openDialog->Show( hDlg ) ) )
 1813              {
 1814                  wil::com_ptr<IShellItem> resultItem;
 1815                  if( SUCCEEDED( openDialog->GetResult( &resultItem ) ) )
 1816                  {
 1817                      wil::unique_cotaskmem_string pathStr;
 1818                      if( SUCCEEDED( resultItem->GetDisplayName( SIGDN_FILESYSPATH, &pathStr ) ) )
 1819                      {
 1820                          _tcscpy( newSoundFile, pathStr.get() );
 1821                          _tcscpy( filePath, pathStr.get() );
 1822                          if( _tcsrchr( filePath, '\\' ) ) _tcscpy( filePath, _tcsrchr( filePath, '\\' ) + 1 );
 1823                          if( _tcsrchr( filePath, '.' ) ) *_tcsrchr( filePath, '.' ) = 0;
 1824                          SetDlgItemText( hDlg, IDC_SOUND_FILE, filePath );
 1825                      }
 1826                  }
 1827              }
 1828  
 1829              openDialog->Unadvise( dwCookie );
 1830              pEvents->Release();
 1831              break;
 1832          }
 1833  
 1834          case IDC_BACKGROUND_BROWSE:
 1835          {
 1836              auto openDialog = wil::CoCreateInstance<IFileOpenDialog>( CLSID_FileOpenDialog );
 1837  
 1838              FILEOPENDIALOGOPTIONS options;
 1839              if( SUCCEEDED( openDialog->GetOptions( &options ) ) )
 1840                  openDialog->SetOptions( options | FOS_FORCEFILESYSTEM );
 1841  
 1842              COMDLG_FILTERSPEC fileTypes[] = {
 1843                  { L"Bitmap Files (*.bmp;*.dib)", L"*.bmp;*.dib" },
 1844                  { L"PNG (*.png)", L"*.png" },
 1845                  { L"JPEG (*.jpg;*.jpeg;*.jpe;*.jfif)", L"*.jpg;*.jpeg;*.jpe;*.jfif" },
 1846                  { L"GIF (*.gif)", L"*.gif" },
 1847                  { L"All Picture Files", L"*.bmp;*.dib;*.png;*.jpg;*.jpeg;*.jpe;*.jfif;*.gif" },
 1848                  { L"All Files", L"*.*" }
 1849              };
 1850              openDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
 1851              openDialog->SetFileTypeIndex( 5 ); // Default to "All Picture Files"
 1852              openDialog->SetTitle( L"ZoomIt: Specify Background File..." );
 1853  
 1854              // Set initial folder
 1855              GetDlgItemText( hDlg, IDC_BACKGROUND_FILE, filePath, _countof( filePath ) );
 1856              if( _tcsrchr( filePath, '\\' ) )
 1857              {
 1858                  _tcscpy( initDir, filePath );
 1859                  *( _tcsrchr( initDir, '\\' ) + 1 ) = 0;
 1860              }
 1861              else
 1862              {
 1863                  _tcscpy( filePath, L"%USERPROFILE%\\Pictures" );
 1864                  ExpandEnvironmentStrings( filePath, initDir, _countof( initDir ) );
 1865              }
 1866              wil::com_ptr<IShellItem> folderItem;
 1867              if( SUCCEEDED( SHCreateItemFromParsingName( initDir, nullptr, IID_PPV_ARGS( &folderItem ) ) ) )
 1868              {
 1869                  openDialog->SetFolder( folderItem.get() );
 1870              }
 1871  
 1872              OpenSaveDialogEvents* pEvents = new OpenSaveDialogEvents(false);
 1873              DWORD dwCookie = 0;
 1874              openDialog->Advise( pEvents, &dwCookie );
 1875  
 1876              if( SUCCEEDED( openDialog->Show( hDlg ) ) )
 1877              {
 1878                  wil::com_ptr<IShellItem> resultItem;
 1879                  if( SUCCEEDED( openDialog->GetResult( &resultItem ) ) )
 1880                  {
 1881                      wil::unique_cotaskmem_string pathStr;
 1882                      if( SUCCEEDED( resultItem->GetDisplayName( SIGDN_FILESYSPATH, &pathStr ) ) )
 1883                      {
 1884                          _tcscpy( newBackgroundFile, pathStr.get() );
 1885                          SetDlgItemText( hDlg, IDC_BACKGROUND_FILE, pathStr.get() );
 1886                      }
 1887                  }
 1888              }
 1889  
 1890              openDialog->Unadvise( dwCookie );
 1891              pEvents->Release();
 1892              break;
 1893          }
 1894  
 1895          case IDOK:
 1896  
 1897              // sound file has to be valid
 1898              g_BreakPlaySoundFile = IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE ) == BST_CHECKED;
 1899              g_BreakShowBackgroundFile = IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE ) == BST_CHECKED;
 1900              g_BreakBackgroundStretch = IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_STRETCH ) == BST_CHECKED;
 1901  #if 0
 1902              g_BreakOnSecondary = IsDlgButtonChecked( hDlg, IDC_CHECK_SECONDARYDISPLAY ) == BST_CHECKED;
 1903  #endif
 1904              if( g_BreakPlaySoundFile && GetFileAttributes( newSoundFile ) == -1 ) {
 1905  
 1906                  MessageBox( hDlg, L"The specified sound file is inaccessible",
 1907                          L"Advanced Break Options Error", MB_ICONERROR );
 1908                  break;
 1909              }
 1910              _tcscpy( g_BreakSoundFile, newSoundFile );
 1911  
 1912              // Background file
 1913              g_BreakShowDesktop = IsDlgButtonChecked( hDlg, IDC_STATIC_DESKTOP_BACKGROUND ) == BST_CHECKED;
 1914  
 1915              if( !g_BreakShowDesktop && g_BreakShowBackgroundFile && GetFileAttributes( newBackgroundFile ) == -1 ) {
 1916  
 1917                  MessageBox( hDlg, L"The specified background file is inaccessible",
 1918                          L"Advanced Break Options Error", MB_ICONERROR );
 1919                  break;
 1920              }
 1921              _tcscpy( g_BreakBackgroundFile, newBackgroundFile );
 1922  
 1923              for( i = 0; i < 10; i++ ) {
 1924  
 1925                  if( IsDlgButtonChecked( hDlg, IDC_TIMER_POS1+i) == BST_CHECKED ) {
 1926  
 1927                      g_BreakTimerPosition = i;
 1928                      break;
 1929                  }
 1930              }
 1931              GetDlgItemText( hDlg, IDC_OPACITY, opacity, sizeof(opacity)/sizeof(opacity[0]));
 1932              _stscanf( opacity, L"%d%%", &g_BreakOpacity );
 1933              reg.WriteRegSettings( RegSettings );
 1934              EndDialog(hDlg, 0);
 1935              break;
 1936  
 1937          case IDCANCEL:
 1938              EndDialog( hDlg, 0 );
 1939              return TRUE;
 1940          }
 1941          break;
 1942  
 1943      default:
 1944          break;
 1945      }
 1946      return FALSE;
 1947  }
 1948  
 1949  
 1950  //----------------------------------------------------------------------------
 1951  //
 1952  // OptionsTabProc
 1953  //
 1954  //----------------------------------------------------------------------------
 1955  
 1956  static UINT_PTR CALLBACK ChooseFontHookProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
 1957  {
 1958      switch (message)
 1959      {
 1960      case WM_INITDIALOG:
 1961          // Set the dialog icon
 1962          {
 1963              HICON hIcon = LoadIcon( g_hInstance, L"APPICON" );
 1964              if( hIcon )
 1965              {
 1966                  SendMessage( hDlg, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIcon) );
 1967                  SendMessage( hDlg, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(hIcon) );
 1968              }
 1969          }
 1970          // Basic (incomplete) dark mode attempt: theme the main common dialog window.
 1971          ApplyDarkModeToDialog(hDlg);
 1972          return 0;
 1973  
 1974      case WM_CTLCOLORDLG:
 1975      case WM_CTLCOLORSTATIC:
 1976      case WM_CTLCOLORBTN:
 1977      case WM_CTLCOLOREDIT:
 1978      case WM_CTLCOLORLISTBOX:
 1979      {
 1980          HDC hdc = reinterpret_cast<HDC>(wParam);
 1981          HWND hCtrl = reinterpret_cast<HWND>(lParam);
 1982          HBRUSH hBrush = HandleDarkModeCtlColor(hdc, hCtrl, message);
 1983          if (hBrush)
 1984          {
 1985              return reinterpret_cast<UINT_PTR>(hBrush);
 1986          }
 1987          break;
 1988      }
 1989      }
 1990  
 1991      return 0;
 1992  }
 1993  
 1994  INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
 1995                                  WPARAM wParam, LPARAM lParam )
 1996  {
 1997      HDC			hDC;
 1998      LOGFONT		lf;
 1999      CHOOSEFONT	chooseFont;
 2000      HFONT		hFont;
 2001      PAINTSTRUCT	ps;
 2002      HWND		hTextPreview;
 2003      HDC			hDc;
 2004      RECT		previewRc;
 2005  
 2006      switch ( message )  {
 2007      case WM_INITDIALOG:
 2008          return TRUE;
 2009  
 2010      case WM_ERASEBKGND:
 2011          if (IsDarkModeEnabled())
 2012          {
 2013              HDC hdc = reinterpret_cast<HDC>(wParam);
 2014              RECT rc;
 2015              GetClientRect(hDlg, &rc);
 2016              FillRect(hdc, &rc, GetDarkModeBrush());
 2017              return TRUE;
 2018          }
 2019          break;
 2020  
 2021      case WM_CTLCOLORDLG:
 2022      case WM_CTLCOLORSTATIC:
 2023      case WM_CTLCOLORBTN:
 2024      case WM_CTLCOLOREDIT:
 2025      case WM_CTLCOLORLISTBOX:
 2026      {
 2027          HDC hdc = reinterpret_cast<HDC>(wParam);
 2028          HWND hCtrl = reinterpret_cast<HWND>(lParam);
 2029          HBRUSH hBrush = HandleDarkModeCtlColor(hdc, hCtrl, message);
 2030          if (hBrush)
 2031          {
 2032              return reinterpret_cast<INT_PTR>(hBrush);
 2033          }
 2034          break;
 2035      }
 2036  
 2037      case WM_COMMAND:
 2038          // Handle combo box selection changes
 2039          if (HIWORD(wParam) == CBN_SELCHANGE) {
 2040              if (LOWORD(wParam) == IDC_RECORD_SCALING) {
 2041  
 2042                  int format = static_cast<int>(SendMessage(GetDlgItem(hDlg, IDC_RECORD_FORMAT), CB_GETCURSEL, 0, 0));
 2043                  int scale = static_cast<int>(SendMessage(GetDlgItem(hDlg, IDC_RECORD_SCALING), CB_GETCURSEL, 0, 0));
 2044                  if(format == 0)
 2045                  {
 2046                      g_RecordScalingGIF = static_cast<BYTE>((scale + 1) * 10);
 2047                  }
 2048                  else
 2049                  {
 2050                      g_RecordScalingMP4 = static_cast<BYTE>((scale + 1) * 10);
 2051                  }
 2052              }
 2053              else if (LOWORD(wParam) == IDC_RECORD_FORMAT) {
 2054                  // Get the currently selected format
 2055                  int selection = static_cast<int>(SendMessage(GetDlgItem(hDlg, IDC_RECORD_FORMAT),
 2056                                                              CB_GETCURSEL, 0, 0));
 2057  
 2058                  // Get the selected text to check if it's GIF
 2059                  TCHAR selectedText[32] = {0};
 2060                  SendMessage(GetDlgItem(hDlg, IDC_RECORD_FORMAT),
 2061                             CB_GETLBTEXT, selection, reinterpret_cast<LPARAM>(selectedText));
 2062  
 2063                  // Check if GIF is selected by comparing the text
 2064                  bool isGifSelected = (wcscmp(selectedText, L"GIF") == 0);
 2065  
 2066                  // If GIF is selected, set the scaling to the g_RecordScalingGIF value
 2067                  if (isGifSelected) {
 2068                      g_RecordScaling = g_RecordScalingGIF;
 2069  
 2070                  } else {
 2071  
 2072                      g_RecordScaling = g_RecordScalingMP4;
 2073                  }
 2074  
 2075                  for (int i = 0; i < 10; i++) {
 2076                      int scalingValue = (i + 1) * 10;
 2077                      if (scalingValue == static_cast<int>(g_RecordScaling)) {
 2078                          SendMessage(GetDlgItem(hDlg, IDC_RECORD_SCALING),
 2079                                      CB_SETCURSEL, i, 0);
 2080                          break;
 2081                      }
 2082                  }
 2083  
 2084                  // Enable/disable audio controls based on selection (GIF has no audio)
 2085                  EnableWindow(GetDlgItem(hDlg, IDC_CAPTURE_SYSTEM_AUDIO), !isGifSelected);
 2086                  EnableWindow(GetDlgItem(hDlg, IDC_CAPTURE_AUDIO), !isGifSelected);
 2087                  EnableWindow(GetDlgItem(hDlg, IDC_MICROPHONE_LABEL), !isGifSelected);
 2088                  EnableWindow(GetDlgItem(hDlg, IDC_MICROPHONE), !isGifSelected);
 2089              }
 2090          }
 2091  
 2092          switch ( LOWORD( wParam )) {
 2093          case IDC_ADVANCED_BREAK:
 2094              DialogBox( g_hInstance, L"ADVANCED_BREAK", hDlg, AdvancedBreakProc );
 2095              break;
 2096          case IDC_FONT:
 2097              hDC = GetDC (hDlg );
 2098              lf = g_LogFont;
 2099              lf.lfHeight = -21;
 2100              chooseFont.hDC = CreateCompatibleDC (hDC);
 2101              ReleaseDC (hDlg, hDC);
 2102              chooseFont.lStructSize = sizeof (CHOOSEFONT);
 2103              chooseFont.hwndOwner = hDlg;
 2104              chooseFont.lpLogFont = &lf;
 2105              chooseFont.Flags     = CF_SCREENFONTS|CF_ENABLETEMPLATE|CF_ENABLEHOOK|
 2106                          CF_INITTOLOGFONTSTRUCT|CF_LIMITSIZE;
 2107              chooseFont.rgbColors = RGB (0, 0, 0);
 2108              chooseFont.lCustData = 0;
 2109              chooseFont.nSizeMin  = 16;
 2110              chooseFont.nSizeMax  = 16;
 2111              chooseFont.hInstance = g_hInstance;
 2112              chooseFont.lpszStyle = static_cast<LPTSTR>(NULL);
 2113              chooseFont.nFontType = SCREEN_FONTTYPE;
 2114              chooseFont.lpfnHook  = ChooseFontHookProc;
 2115              chooseFont.lpTemplateName = static_cast<LPTSTR>(MAKEINTRESOURCE (FORMATDLGORD31));
 2116              if( ChooseFont( &chooseFont ) ) {
 2117                  g_LogFont = lf;
 2118                  InvalidateRect( hDlg, NULL, TRUE );
 2119              }
 2120              break;
 2121          case IDC_DEMOTYPE_BROWSE:
 2122          {
 2123              auto openDialog = wil::CoCreateInstance<IFileOpenDialog>( CLSID_FileOpenDialog );
 2124  
 2125              FILEOPENDIALOGOPTIONS options;
 2126              if( SUCCEEDED( openDialog->GetOptions( &options ) ) )
 2127                  openDialog->SetOptions( options | FOS_FORCEFILESYSTEM );
 2128  
 2129              COMDLG_FILTERSPEC fileTypes[] = {
 2130                  { L"All Files", L"*.*" }
 2131              };
 2132              openDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
 2133              openDialog->SetFileTypeIndex( 1 );
 2134              openDialog->SetTitle( L"ZoomIt: Specify DemoType File..." );
 2135  
 2136              OpenSaveDialogEvents* pEvents = new OpenSaveDialogEvents(false);
 2137              DWORD dwCookie = 0;
 2138              openDialog->Advise( pEvents, &dwCookie );
 2139  
 2140              if( SUCCEEDED( openDialog->Show( hDlg ) ) )
 2141              {
 2142                  wil::com_ptr<IShellItem> resultItem;
 2143                  if( SUCCEEDED( openDialog->GetResult( &resultItem ) ) )
 2144                  {
 2145                      wil::unique_cotaskmem_string pathStr;
 2146                      if( SUCCEEDED( resultItem->GetDisplayName( SIGDN_FILESYSPATH, &pathStr ) ) )
 2147                      {
 2148                          if( GetFileAttributes( pathStr.get() ) == INVALID_FILE_ATTRIBUTES )
 2149                          {
 2150                              MessageBox( hDlg, L"The specified file is inaccessible", APPNAME, MB_ICONERROR );
 2151                          }
 2152                          else
 2153                          {
 2154                              SetDlgItemText( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_FILE, pathStr.get() );
 2155                              _tcscpy( g_DemoTypeFile, pathStr.get() );
 2156                          }
 2157                      }
 2158                  }
 2159              }
 2160  
 2161              openDialog->Unadvise( dwCookie );
 2162              pEvents->Release();
 2163              break;
 2164          }
 2165          }
 2166          break;
 2167  
 2168      case WM_PAINT:
 2169          if( (hTextPreview = GetDlgItem( hDlg, IDC_TEXT_FONT )) != 0 ) {
 2170  
 2171              // 16-pt preview
 2172              LOGFONT _lf = g_LogFont;
 2173              _lf.lfHeight = -21;
 2174              hFont = CreateFontIndirect( &_lf);
 2175              hDc = BeginPaint(hDlg, &ps);
 2176              SelectObject( hDc, hFont );
 2177  
 2178              GetWindowRect( hTextPreview, &previewRc );
 2179              MapWindowPoints( NULL, hDlg, reinterpret_cast<LPPOINT>(&previewRc), 2);
 2180  
 2181              previewRc.top += 6;
 2182  
 2183              // Set text color based on dark mode
 2184              if (IsDarkModeEnabled())
 2185              {
 2186                  SetTextColor(hDc, DarkMode::TextColor);
 2187                  SetBkMode(hDc, TRANSPARENT);
 2188              }
 2189  
 2190              DrawText( hDc, L"AaBbYyZz", static_cast<int>(_tcslen(L"AaBbYyZz")), &previewRc,
 2191                  DT_CENTER|DT_VCENTER|DT_SINGLELINE );
 2192  
 2193              EndPaint( hDlg, &ps );
 2194              DeleteObject( hFont );
 2195          }
 2196          break;
 2197      default:
 2198          break;
 2199      }
 2200      return FALSE;
 2201  }
 2202  
 2203  
 2204  //----------------------------------------------------------------------------
 2205  //
 2206  // RepositionTabPages
 2207  //
 2208  // Reposition tab pages to fit current tab control size (call after DPI change)
 2209  //
 2210  //----------------------------------------------------------------------------
 2211  VOID RepositionTabPages( HWND hTabCtrl )
 2212  {
 2213      RECT rc, pageRc;
 2214      GetWindowRect( hTabCtrl, &rc );
 2215      TabCtrl_AdjustRect( hTabCtrl, FALSE, &rc );
 2216  
 2217      // Inset the display area to leave room for border in dark mode
 2218      if (IsDarkModeEnabled())
 2219      {
 2220          rc.left += 2;
 2221          rc.top += 2;
 2222          rc.right -= 2;
 2223          rc.bottom -= 2;
 2224      }
 2225  
 2226      // Get the parent dialog to convert coordinates correctly
 2227      HWND hParent = GetParent( hTabCtrl );
 2228  
 2229      for( int i = 0; i < sizeof( g_OptionsTabs )/sizeof(g_OptionsTabs[0]); i++ ) {
 2230          if( g_OptionsTabs[i].hPage ) {
 2231              pageRc = rc;
 2232              // Convert screen coords to parent dialog client coords
 2233              MapWindowPoints( NULL, hParent, reinterpret_cast<LPPOINT>(&pageRc), 2);
 2234  
 2235              SetWindowPos( g_OptionsTabs[i].hPage,
 2236                   HWND_TOP,
 2237                   pageRc.left, pageRc.top,
 2238                   pageRc.right - pageRc.left, pageRc.bottom - pageRc.top,
 2239                   SWP_NOACTIVATE | SWP_NOZORDER );
 2240          }
 2241      }
 2242  }
 2243  
 2244  //----------------------------------------------------------------------------
 2245  //
 2246  // OptionsAddTabs
 2247  //
 2248  //----------------------------------------------------------------------------
 2249  VOID OptionsAddTabs( HWND hOptionsDlg, HWND hTabCtrl )
 2250  {
 2251      int		i;
 2252      TCITEM	tcItem;
 2253      RECT	rc, pageRc;
 2254  
 2255      GetWindowRect( hTabCtrl, &rc );
 2256      for( i = 0; i < sizeof( g_OptionsTabs )/sizeof(g_OptionsTabs[0]); i++ ) {
 2257  
 2258          tcItem.mask = TCIF_TEXT;
 2259          tcItem.pszText = g_OptionsTabs[i].TabTitle;
 2260          TabCtrl_InsertItem( hTabCtrl, i, &tcItem );
 2261          g_OptionsTabs[i].hPage = CreateDialog( g_hInstance, g_OptionsTabs[i].TabTitle,
 2262                      hOptionsDlg, OptionsTabProc );
 2263      }
 2264  
 2265      TabCtrl_AdjustRect( hTabCtrl, FALSE, &rc );
 2266  
 2267      // Inset the display area to leave room for border in dark mode
 2268      // Need 2 pixels so tab pages don't cover the 1-pixel border
 2269      if (IsDarkModeEnabled())
 2270      {
 2271          rc.left += 2;
 2272          rc.top += 2;
 2273          rc.right -= 2;
 2274          rc.bottom -= 2;
 2275      }
 2276  
 2277      for( i = 0; i < sizeof( g_OptionsTabs )/sizeof(g_OptionsTabs[0]); i++ ) {
 2278  
 2279          pageRc = rc;
 2280          // Convert screen coords to parent dialog client coords.
 2281          MapWindowPoints( NULL, hOptionsDlg, reinterpret_cast<LPPOINT>(&pageRc), 2);
 2282  
 2283          SetWindowPos( g_OptionsTabs[i].hPage,
 2284               HWND_TOP,
 2285               pageRc.left, pageRc.top,
 2286               pageRc.right - pageRc.left, pageRc.bottom - pageRc.top,
 2287               SWP_NOACTIVATE | SWP_HIDEWINDOW );
 2288  
 2289          if( pEnableThemeDialogTexture ) {
 2290              if( IsDarkModeEnabled() ) {
 2291                  // Disable theme dialog texture in dark mode - it interferes with dark backgrounds
 2292                  pEnableThemeDialogTexture( g_OptionsTabs[i].hPage, ETDT_DISABLE );
 2293              } else {
 2294                  // Enable tab texturing in light mode
 2295                  pEnableThemeDialogTexture( g_OptionsTabs[i].hPage, ETDT_ENABLETAB );
 2296              }
 2297          }
 2298      }
 2299  
 2300      // Show the initial page once positioned to reduce visible churn.
 2301      if( g_OptionsTabs[0].hPage )
 2302      {
 2303          ShowWindow( g_OptionsTabs[0].hPage, SW_SHOW );
 2304      }
 2305  }
 2306  
 2307  //----------------------------------------------------------------------------
 2308  //
 2309  // UnregisterAllHotkeys
 2310  //
 2311  //----------------------------------------------------------------------------
 2312  void UnregisterAllHotkeys( HWND hWnd )
 2313  {
 2314      UnregisterHotKey( hWnd, ZOOM_HOTKEY);
 2315      UnregisterHotKey( hWnd, LIVE_HOTKEY);
 2316      UnregisterHotKey( hWnd, LIVE_DRAW_HOTKEY);
 2317      UnregisterHotKey( hWnd, DRAW_HOTKEY);
 2318      UnregisterHotKey( hWnd, BREAK_HOTKEY);
 2319      UnregisterHotKey( hWnd, RECORD_HOTKEY);
 2320      UnregisterHotKey( hWnd, RECORD_CROP_HOTKEY );
 2321      UnregisterHotKey( hWnd, RECORD_WINDOW_HOTKEY );
 2322      UnregisterHotKey( hWnd, SNIP_HOTKEY );
 2323      UnregisterHotKey( hWnd, SNIP_SAVE_HOTKEY);
 2324      UnregisterHotKey( hWnd, DEMOTYPE_HOTKEY );
 2325      UnregisterHotKey( hWnd, DEMOTYPE_RESET_HOTKEY );
 2326      UnregisterHotKey( hWnd, RECORD_GIF_HOTKEY );
 2327      UnregisterHotKey( hWnd, RECORD_GIF_WINDOW_HOTKEY );
 2328      UnregisterHotKey( hWnd, SAVE_IMAGE_HOTKEY );
 2329      UnregisterHotKey( hWnd, SAVE_CROP_HOTKEY );
 2330      UnregisterHotKey( hWnd, COPY_IMAGE_HOTKEY );
 2331      UnregisterHotKey( hWnd, COPY_CROP_HOTKEY );
 2332  }
 2333  
 2334  //----------------------------------------------------------------------------
 2335  //
 2336  // RegisterAllHotkeys
 2337  //
 2338  //----------------------------------------------------------------------------
 2339  void RegisterAllHotkeys(HWND hWnd)
 2340  {
 2341      if (g_ToggleKey) 			RegisterHotKey(hWnd, ZOOM_HOTKEY, g_ToggleMod, g_ToggleKey & 0xFF);
 2342      if (g_LiveZoomToggleKey) {
 2343          RegisterHotKey(hWnd, LIVE_HOTKEY, g_LiveZoomToggleMod, g_LiveZoomToggleKey & 0xFF);
 2344          RegisterHotKey(hWnd, LIVE_DRAW_HOTKEY, (g_LiveZoomToggleMod ^ MOD_SHIFT), g_LiveZoomToggleKey & 0xFF);
 2345      }
 2346      if (g_DrawToggleKey) 		RegisterHotKey(hWnd, DRAW_HOTKEY, g_DrawToggleMod, g_DrawToggleKey & 0xFF);
 2347      if (g_BreakToggleKey) 		RegisterHotKey(hWnd, BREAK_HOTKEY, g_BreakToggleMod, g_BreakToggleKey & 0xFF);
 2348      if (g_DemoTypeToggleKey) {
 2349          RegisterHotKey(hWnd, DEMOTYPE_HOTKEY, g_DemoTypeToggleMod, g_DemoTypeToggleKey & 0xFF);
 2350          RegisterHotKey(hWnd, DEMOTYPE_RESET_HOTKEY, (g_DemoTypeToggleMod ^ MOD_SHIFT), g_DemoTypeToggleKey & 0xFF);
 2351      }
 2352      if (g_SnipToggleKey) {
 2353          RegisterHotKey(hWnd, SNIP_HOTKEY, g_SnipToggleMod, g_SnipToggleKey & 0xFF);
 2354          RegisterHotKey(hWnd, SNIP_SAVE_HOTKEY, (g_SnipToggleMod ^ MOD_SHIFT), g_SnipToggleKey & 0xFF);
 2355      }
 2356      if (g_RecordToggleKey) {
 2357          RegisterHotKey(hWnd, RECORD_HOTKEY, g_RecordToggleMod | MOD_NOREPEAT, g_RecordToggleKey & 0xFF);
 2358          RegisterHotKey(hWnd, RECORD_CROP_HOTKEY, (g_RecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF);
 2359          RegisterHotKey(hWnd, RECORD_WINDOW_HOTKEY, (g_RecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF);
 2360      }
 2361      // Register CTRL+8 for GIF recording and CTRL+ALT+8 for GIF window recording
 2362      RegisterHotKey(hWnd, RECORD_GIF_HOTKEY, MOD_CONTROL | MOD_NOREPEAT, 568 && 0xFF);
 2363      RegisterHotKey(hWnd, RECORD_GIF_WINDOW_HOTKEY, MOD_CONTROL | MOD_ALT | MOD_NOREPEAT, 568 && 0xFF);
 2364  
 2365      // Note: COPY_IMAGE_HOTKEY, COPY_CROP_HOTKEY (Ctrl+C, Ctrl+Shift+C) and
 2366      // SAVE_IMAGE_HOTKEY, SAVE_CROP_HOTKEY (Ctrl+S, Ctrl+Shift+S) are registered
 2367      // only during static zoom mode to avoid blocking system-wide Ctrl+C/Ctrl+S
 2368  }
 2369  
 2370  
 2371  
 2372  //----------------------------------------------------------------------------
 2373  //
 2374  // UpdateDrawTabHeaderFont
 2375  //
 2376  //----------------------------------------------------------------------------
 2377  void UpdateDrawTabHeaderFont()
 2378  {
 2379      static HFONT	headerFont = nullptr;
 2380      TCHAR 			text[64];
 2381  
 2382      constexpr int headers[] = { IDC_PEN_CONTROL, IDC_COLORS, IDC_HIGHLIGHT_AND_BLUR, IDC_SHAPES, IDC_SCREEN };
 2383  
 2384      HWND hPage = g_OptionsTabs[DRAW_PAGE].hPage;
 2385      if( !hPage )
 2386      {
 2387          return;
 2388      }
 2389  
 2390      // Get the font from an actual body text control that has been DPI-scaled.
 2391      // This ensures headers use the exact same font as body text, just bold.
 2392      // Find the first static text child control (ID -1) to get the scaled body text font.
 2393      HFONT hBaseFont = nullptr;
 2394      HWND hChild = GetWindow( hPage, GW_CHILD );
 2395      while( hChild != nullptr )
 2396      {
 2397          if( GetDlgCtrlID( hChild ) == -1 )  // IDC_STATIC is -1
 2398          {
 2399              hBaseFont = reinterpret_cast<HFONT>(SendMessage( hChild, WM_GETFONT, 0, 0 ));
 2400              if( hBaseFont )
 2401              {
 2402                  break;
 2403              }
 2404          }
 2405          hChild = GetWindow( hChild, GW_HWNDNEXT );
 2406      }
 2407  
 2408      if( !hBaseFont )
 2409      {
 2410          hBaseFont = static_cast<HFONT>(GetStockObject( DEFAULT_GUI_FONT ));
 2411      }
 2412  
 2413      LOGFONT lf{};
 2414      if( !GetObject( hBaseFont, sizeof( LOGFONT ), &lf ) )
 2415      {
 2416          GetObject( GetStockObject( DEFAULT_GUI_FONT ), sizeof( LOGFONT ), &lf );
 2417      }
 2418      lf.lfWeight = FW_BOLD;
 2419  
 2420      HFONT newHeaderFont = CreateFontIndirect( &lf );
 2421      if( !newHeaderFont )
 2422      {
 2423          return;
 2424      }
 2425  
 2426      // Swap fonts safely: apply the new font to all header controls first, then delete the old.
 2427      HFONT oldHeaderFont = headerFont;
 2428      headerFont = newHeaderFont;
 2429  
 2430      for( int i = 0; i < _countof( headers ); i++ )
 2431      {
 2432          // Change the header font to bold
 2433          HWND hHeader = GetDlgItem( hPage, headers[i] );
 2434          if( !hHeader )
 2435          {
 2436              continue;
 2437          }
 2438  
 2439          // StaticTextSubclassProc already supports a per-control font override via this property.
 2440          // Setting it here makes Draw tab headers resilient if something later overwrites WM_SETFONT.
 2441          SetPropW( hHeader, L"ZoomIt.HeaderFont", headerFont );
 2442  
 2443          SendMessage( hHeader, WM_SETFONT, reinterpret_cast<WPARAM>(headerFont), TRUE );
 2444  
 2445          // Resize the control to fit the text
 2446          GetWindowText( hHeader, text, sizeof( text ) / sizeof( text[0] ) );
 2447          RECT rc;
 2448          GetWindowRect( hHeader, &rc );
 2449          MapWindowPoints( NULL, g_OptionsTabs[DRAW_PAGE].hPage, reinterpret_cast<LPPOINT>(&rc), 2 );
 2450          HDC hDC = GetDC( hHeader );
 2451          SelectFont( hDC, headerFont );
 2452          DrawText( hDC, text, static_cast<int>(_tcslen( text )), &rc, DT_CALCRECT | DT_SINGLELINE | DT_LEFT | DT_VCENTER );
 2453          ReleaseDC( hHeader, hDC );
 2454          SetWindowPos( hHeader, nullptr, 0, 0, rc.right - rc.left + ScaleForDpi( 4, GetDpiForWindowHelper( hHeader ) ), rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER );
 2455          InvalidateRect( hHeader, nullptr, TRUE );
 2456      }
 2457  
 2458      if( oldHeaderFont )
 2459      {
 2460          DeleteObject( oldHeaderFont );
 2461      }
 2462  }
 2463  
 2464  //----------------------------------------------------------------------------
 2465  //
 2466  // CheckboxSubclassProc
 2467  //
 2468  // Subclass procedure for checkbox and radio button controls to handle dark mode colors
 2469  //
 2470  //----------------------------------------------------------------------------
 2471  LRESULT CALLBACK CheckboxSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
 2472  {
 2473      switch (uMsg)
 2474      {
 2475      case WM_PAINT:
 2476          if (IsDarkModeEnabled())
 2477          {
 2478              TCHAR dbgText[256] = { 0 };
 2479              GetWindowText(hWnd, dbgText, _countof(dbgText));
 2480              bool dbgEnabled = IsWindowEnabled(hWnd);
 2481              LONG dbgStyle = GetWindowLong(hWnd, GWL_STYLE);
 2482              LONG dbgType = dbgStyle & BS_TYPEMASK;
 2483              bool dbgIsRadio = (dbgType == BS_RADIOBUTTON || dbgType == BS_AUTORADIOBUTTON);
 2484              OutputDebugStringW((std::wstring(L"[Checkbox] WM_PAINT: ") + dbgText +
 2485                  L" enabled=" + (dbgEnabled ? L"1" : L"0") +
 2486                  L" isRadio=" + (dbgIsRadio ? L"1" : L"0") + L"\n").c_str());
 2487  
 2488              PAINTSTRUCT ps;
 2489              HDC hdc = BeginPaint(hWnd, &ps);
 2490  
 2491              RECT rc;
 2492              GetClientRect(hWnd, &rc);
 2493  
 2494              // Fill background
 2495              FillRect(hdc, &rc, GetDarkModeBrush());
 2496  
 2497              // Get button state and style
 2498              LRESULT state = SendMessage(hWnd, BM_GETCHECK, 0, 0);
 2499              bool isChecked = (state == BST_CHECKED);
 2500              bool isEnabled = IsWindowEnabled(hWnd);
 2501  
 2502              // Check if this is a radio button
 2503              LONG style = GetWindowLong(hWnd, GWL_STYLE);
 2504              LONG buttonType = style & BS_TYPEMASK;
 2505              bool isRadioButton = (buttonType == BS_RADIOBUTTON || buttonType == BS_AUTORADIOBUTTON);
 2506  
 2507              // Check if checkbox should be on the right (BS_LEFTTEXT or WS_EX_RIGHT)
 2508              bool checkOnRight = (style & BS_LEFTTEXT) != 0;
 2509              LONG exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
 2510              if (exStyle & WS_EX_RIGHT)
 2511                  checkOnRight = true;
 2512  
 2513              // Get DPI for scaling
 2514              UINT dpi = GetDpiForWindowHelper(hWnd);
 2515              int checkSize = ScaleForDpi(13, dpi);
 2516              int margin = ScaleForDpi(2, dpi);
 2517  
 2518              // Calculate checkbox/radio position
 2519              RECT rcCheck;
 2520              if (checkOnRight)
 2521              {
 2522                  rcCheck.right = rc.right - margin;
 2523                  rcCheck.left = rcCheck.right - checkSize;
 2524              }
 2525              else
 2526              {
 2527                  rcCheck.left = rc.left + margin;
 2528                  rcCheck.right = rcCheck.left + checkSize;
 2529              }
 2530              rcCheck.top = rc.top + (rc.bottom - rc.top - checkSize) / 2;
 2531              rcCheck.bottom = rcCheck.top + checkSize;
 2532  
 2533              // Choose colors based on enabled state
 2534              COLORREF borderColor = isEnabled ? DarkMode::BorderColor : RGB(60, 60, 60);
 2535              COLORREF fillColor = isChecked ? (isEnabled ? DarkMode::AccentColor : RGB(80, 80, 85)) : DarkMode::SurfaceColor;
 2536              COLORREF textColor = isEnabled ? DarkMode::TextColor : RGB(100, 100, 100);
 2537  
 2538              if (isRadioButton)
 2539              {
 2540                  // Draw radio button (circle)
 2541                  HPEN hPen = CreatePen(PS_SOLID, 1, borderColor);
 2542                  HPEN hOldPen = static_cast<HPEN>(SelectObject(hdc, hPen));
 2543                  HBRUSH hFillBrush = CreateSolidBrush(isChecked ? fillColor : DarkMode::SurfaceColor);
 2544                  HBRUSH hOldBrush = static_cast<HBRUSH>(SelectObject(hdc, hFillBrush));
 2545                  Ellipse(hdc, rcCheck.left, rcCheck.top, rcCheck.right, rcCheck.bottom);
 2546                  SelectObject(hdc, hOldBrush);
 2547                  SelectObject(hdc, hOldPen);
 2548                  DeleteObject(hPen);
 2549                  DeleteObject(hFillBrush);
 2550  
 2551                  // Draw inner circle if checked
 2552                  if (isChecked)
 2553                  {
 2554                      int innerMargin = ScaleForDpi(3, dpi);
 2555                      HBRUSH hInnerBrush = CreateSolidBrush(isEnabled ? RGB(255, 255, 255) : RGB(140, 140, 140));
 2556                      HBRUSH hOldInnerBrush = static_cast<HBRUSH>(SelectObject(hdc, hInnerBrush));
 2557                      HPEN hNullPen = static_cast<HPEN>(SelectObject(hdc, GetStockObject(NULL_PEN)));
 2558                      Ellipse(hdc, rcCheck.left + innerMargin, rcCheck.top + innerMargin,
 2559                              rcCheck.right - innerMargin, rcCheck.bottom - innerMargin);
 2560                      SelectObject(hdc, hNullPen);
 2561                      SelectObject(hdc, hOldInnerBrush);
 2562                      DeleteObject(hInnerBrush);
 2563                  }
 2564              }
 2565              else
 2566              {
 2567                  // Draw checkbox (rectangle)
 2568                  HPEN hPen = CreatePen(PS_SOLID, 1, borderColor);
 2569                  HPEN hOldPen = static_cast<HPEN>(SelectObject(hdc, hPen));
 2570                  HBRUSH hFillBrush = CreateSolidBrush(fillColor);
 2571                  HBRUSH hOldBrush = static_cast<HBRUSH>(SelectObject(hdc, hFillBrush));
 2572                  Rectangle(hdc, rcCheck.left, rcCheck.top, rcCheck.right, rcCheck.bottom);
 2573                  SelectObject(hdc, hOldBrush);
 2574                  SelectObject(hdc, hOldPen);
 2575                  DeleteObject(hPen);
 2576                  DeleteObject(hFillBrush);
 2577  
 2578                  // Draw checkmark if checked
 2579                  if (isChecked)
 2580                  {
 2581                      COLORREF checkColor = isEnabled ? RGB(255, 255, 255) : RGB(140, 140, 140);
 2582                      HPEN hCheckPen = CreatePen(PS_SOLID, ScaleForDpi(2, dpi), checkColor);
 2583                      HPEN hOldCheckPen = static_cast<HPEN>(SelectObject(hdc, hCheckPen));
 2584  
 2585                      // Draw checkmark
 2586                      int x = rcCheck.left + ScaleForDpi(3, dpi);
 2587                      int y = rcCheck.top + ScaleForDpi(6, dpi);
 2588                      MoveToEx(hdc, x, y, nullptr);
 2589                      LineTo(hdc, x + ScaleForDpi(2, dpi), y + ScaleForDpi(3, dpi));
 2590                      LineTo(hdc, x + ScaleForDpi(7, dpi), y - ScaleForDpi(3, dpi));
 2591  
 2592                      SelectObject(hdc, hOldCheckPen);
 2593                      DeleteObject(hCheckPen);
 2594                  }
 2595              }
 2596  
 2597              // Draw text
 2598              TCHAR text[256] = { 0 };
 2599              GetWindowText(hWnd, text, _countof(text));
 2600  
 2601              SetBkMode(hdc, TRANSPARENT);
 2602              SetTextColor(hdc, textColor);
 2603              HFONT hFont = reinterpret_cast<HFONT>(SendMessage(hWnd, WM_GETFONT, 0, 0));
 2604              HFONT hOldFont = nullptr;
 2605              if (hFont)
 2606              {
 2607                  hOldFont = static_cast<HFONT>(SelectObject(hdc, hFont));
 2608              }
 2609  
 2610              RECT rcText = rc;
 2611              UINT textFormat = DT_VCENTER | DT_SINGLELINE;
 2612              if (checkOnRight)
 2613              {
 2614                  rcText.right = rcCheck.left - ScaleForDpi(4, dpi);
 2615                  textFormat |= DT_RIGHT;
 2616              }
 2617              else
 2618              {
 2619                  rcText.left = rcCheck.right + ScaleForDpi(4, dpi);
 2620                  textFormat |= DT_LEFT;
 2621              }
 2622              DrawText(hdc, text, -1, &rcText, textFormat);
 2623  
 2624              if (hOldFont)
 2625              {
 2626                  SelectObject(hdc, hOldFont);
 2627              }
 2628  
 2629              EndPaint(hWnd, &ps);
 2630              return 0;
 2631          }
 2632          break;
 2633  
 2634      case WM_ENABLE:
 2635          if (IsDarkModeEnabled())
 2636          {
 2637              // Let base window proc handle enable state change, but avoid any subclass chain
 2638              // that might trigger themed drawing
 2639              LRESULT result = DefWindowProc(hWnd, uMsg, wParam, lParam);
 2640              // Force immediate repaint with our custom painting
 2641              InvalidateRect(hWnd, nullptr, TRUE);
 2642              UpdateWindow(hWnd);
 2643              return result;
 2644          }
 2645          break;
 2646  
 2647      case WM_NCDESTROY:
 2648          RemoveWindowSubclass(hWnd, CheckboxSubclassProc, uIdSubclass);
 2649          break;
 2650      }
 2651      return DefSubclassProc(hWnd, uMsg, wParam, lParam);
 2652  }
 2653  
 2654  //----------------------------------------------------------------------------
 2655  //
 2656  // HotkeyControlSubclassProc
 2657  //
 2658  // Subclass procedure for hotkey controls to handle dark mode colors
 2659  //
 2660  //----------------------------------------------------------------------------
 2661  LRESULT CALLBACK HotkeyControlSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
 2662  {
 2663      switch (uMsg)
 2664      {
 2665      case WM_PAINT:
 2666          if (IsDarkModeEnabled())
 2667          {
 2668              // Get the hotkey from the control using HKM_GETHOTKEY
 2669              LRESULT hk = SendMessage(hWnd, HKM_GETHOTKEY, 0, 0);
 2670              WORD hotkey = LOWORD(hk);
 2671              BYTE vk = LOBYTE(hotkey);
 2672              BYTE mods = HIBYTE(hotkey);
 2673  
 2674              // Build the hotkey text
 2675              std::wstring text;
 2676              if (vk != 0)
 2677              {
 2678                  if (mods & HOTKEYF_CONTROL)
 2679                      text += L"Ctrl+";
 2680                  if (mods & HOTKEYF_SHIFT)
 2681                      text += L"Shift+";
 2682                  if (mods & HOTKEYF_ALT)
 2683                      text += L"Alt+";
 2684  
 2685                  // Get key name using virtual key code
 2686                  UINT scanCode = MapVirtualKeyW(vk, MAPVK_VK_TO_VSC);
 2687                  if (scanCode != 0)
 2688                  {
 2689                      TCHAR keyName[64] = { 0 };
 2690                      LONG lParamKey = (scanCode << 16);
 2691                      // Set extended key bit for certain keys
 2692                      if ((vk >= VK_PRIOR && vk <= VK_DELETE) ||
 2693                          (vk >= VK_LWIN && vk <= VK_APPS) ||
 2694                          vk == VK_DIVIDE || vk == VK_NUMLOCK)
 2695                      {
 2696                          lParamKey |= (1 << 24);
 2697                      }
 2698                      if (GetKeyNameTextW(lParamKey, keyName, _countof(keyName)) > 0)
 2699                      {
 2700                          text += keyName;
 2701                      }
 2702                      else
 2703                      {
 2704                          // Fallback: use the virtual key character for printable keys
 2705                          if (vk >= '0' && vk <= '9')
 2706                          {
 2707                              text += static_cast<wchar_t>(vk);
 2708                          }
 2709                          else if (vk >= 'A' && vk <= 'Z')
 2710                          {
 2711                              text += static_cast<wchar_t>(vk);
 2712                          }
 2713                          else if (vk >= VK_F1 && vk <= VK_F24)
 2714                          {
 2715                              text += L"F";
 2716                              text += std::to_wstring(vk - VK_F1 + 1);
 2717                          }
 2718                      }
 2719                  }
 2720                  else
 2721                  {
 2722                      // No scan code, try direct character representation
 2723                      if (vk >= '0' && vk <= '9')
 2724                      {
 2725                          text += static_cast<wchar_t>(vk);
 2726                      }
 2727                      else if (vk >= 'A' && vk <= 'Z')
 2728                      {
 2729                          text += static_cast<wchar_t>(vk);
 2730                      }
 2731                      else if (vk >= VK_F1 && vk <= VK_F24)
 2732                      {
 2733                          text += L"F";
 2734                          text += std::to_wstring(vk - VK_F1 + 1);
 2735                      }
 2736                  }
 2737              }
 2738  
 2739              PAINTSTRUCT ps;
 2740              HDC hdc = BeginPaint(hWnd, &ps);
 2741  
 2742              RECT rc;
 2743              GetClientRect(hWnd, &rc);
 2744  
 2745              // Fill background with dark surface color
 2746              FillRect(hdc, &rc, GetDarkModeSurfaceBrush());
 2747  
 2748              // No border in dark mode - just the filled background
 2749  
 2750              // Draw text if we have any
 2751              if (!text.empty())
 2752              {
 2753                  SetBkMode(hdc, TRANSPARENT);
 2754                  SetTextColor(hdc, DarkMode::TextColor);
 2755                  HFONT hFont = reinterpret_cast<HFONT>(SendMessage(hWnd, WM_GETFONT, 0, 0));
 2756                  HFONT hOldFont = nullptr;
 2757                  if (hFont)
 2758                  {
 2759                      hOldFont = static_cast<HFONT>(SelectObject(hdc, hFont));
 2760                  }
 2761                  RECT rcText = rc;
 2762                  rcText.left += 4;
 2763                  rcText.right -= 4;
 2764                  DrawTextW(hdc, text.c_str(), -1, &rcText, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
 2765                  if (hOldFont)
 2766                  {
 2767                      SelectObject(hdc, hOldFont);
 2768                  }
 2769              }
 2770  
 2771              EndPaint(hWnd, &ps);
 2772              return 0;
 2773          }
 2774          break;
 2775  
 2776      case WM_NCPAINT:
 2777          if (IsDarkModeEnabled())
 2778          {
 2779              // Fill the non-client area with background color to hide the border
 2780              HDC hdc = GetWindowDC(hWnd);
 2781              if (hdc)
 2782              {
 2783                  RECT rc;
 2784                  GetWindowRect(hWnd, &rc);
 2785                  int width = rc.right - rc.left;
 2786                  int height = rc.bottom - rc.top;
 2787                  rc.left = 0;
 2788                  rc.top = 0;
 2789                  rc.right = width;
 2790                  rc.bottom = height;
 2791  
 2792                  // Get NC border size
 2793                  RECT rcClient;
 2794                  GetClientRect(hWnd, &rcClient);
 2795                  MapWindowPoints(hWnd, nullptr, reinterpret_cast<LPPOINT>(&rcClient), 2);
 2796  
 2797                  RECT rcWindow;
 2798                  GetWindowRect(hWnd, &rcWindow);
 2799  
 2800                  int borderLeft = rcClient.left - rcWindow.left;
 2801                  int borderTop = rcClient.top - rcWindow.top;
 2802                  int borderRight = rcWindow.right - rcClient.right;
 2803                  int borderBottom = rcWindow.bottom - rcClient.bottom;
 2804  
 2805                  // Fill the entire NC border area with background color
 2806                  HBRUSH hBrush = CreateSolidBrush(DarkMode::BackgroundColor);
 2807  
 2808                  // Top border
 2809                  RECT rcTop = { 0, 0, width, borderTop };
 2810                  FillRect(hdc, &rcTop, hBrush);
 2811  
 2812                  // Bottom border
 2813                  RECT rcBottom = { 0, height - borderBottom, width, height };
 2814                  FillRect(hdc, &rcBottom, hBrush);
 2815  
 2816                  // Left border
 2817                  RECT rcLeft = { 0, borderTop, borderLeft, height - borderBottom };
 2818                  FillRect(hdc, &rcLeft, hBrush);
 2819  
 2820                  // Right border
 2821                  RECT rcRight = { width - borderRight, borderTop, width, height - borderBottom };
 2822                  FillRect(hdc, &rcRight, hBrush);
 2823  
 2824                  DeleteObject(hBrush);
 2825  
 2826                  // Draw thin border around the control
 2827                  HPEN hPen = CreatePen(PS_SOLID, 1, DarkMode::BorderColor);
 2828                  HPEN hOldPen = static_cast<HPEN>(SelectObject(hdc, hPen));
 2829                  HBRUSH hOldBrush = static_cast<HBRUSH>(SelectObject(hdc, GetStockObject(NULL_BRUSH)));
 2830                  Rectangle(hdc, 0, 0, width, height);
 2831                  SelectObject(hdc, hOldBrush);
 2832                  SelectObject(hdc, hOldPen);
 2833                  DeleteObject(hPen);
 2834  
 2835                  ReleaseDC(hWnd, hdc);
 2836              }
 2837              return 0;
 2838          }
 2839          break;
 2840  
 2841      case WM_NCDESTROY:
 2842          RemoveWindowSubclass(hWnd, HotkeyControlSubclassProc, uIdSubclass);
 2843          break;
 2844      }
 2845      return DefSubclassProc(hWnd, uMsg, wParam, lParam);
 2846  }
 2847  
 2848  //----------------------------------------------------------------------------
 2849  //
 2850  // EditControlSubclassProc
 2851  //
 2852  // Subclass procedure for edit controls to handle dark mode (no border)
 2853  //
 2854  //----------------------------------------------------------------------------
 2855  LRESULT CALLBACK EditControlSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
 2856  {
 2857      // Helper to adjust formatting rectangle for vertical text centering
 2858      auto AdjustTextRect = [](HWND hEdit) {
 2859          RECT rcClient;
 2860          GetClientRect(hEdit, &rcClient);
 2861  
 2862          // Get font metrics to calculate text height
 2863          HDC hdc = GetDC(hEdit);
 2864          HFONT hFont = reinterpret_cast<HFONT>(SendMessage(hEdit, WM_GETFONT, 0, 0));
 2865          HFONT hOldFont = hFont ? static_cast<HFONT>(SelectObject(hdc, hFont)) : nullptr;
 2866  
 2867          TEXTMETRIC tm;
 2868          GetTextMetrics(hdc, &tm);
 2869          int textHeight = tm.tmHeight;
 2870  
 2871          if (hOldFont)
 2872              SelectObject(hdc, hOldFont);
 2873          ReleaseDC(hEdit, hdc);
 2874  
 2875          // Calculate vertical offset to center text
 2876          int clientHeight = rcClient.bottom - rcClient.top;
 2877          int topOffset = (clientHeight - textHeight) / 2;
 2878          if (topOffset < 0) topOffset = 0;
 2879  
 2880          RECT rcFormat = rcClient;
 2881          rcFormat.top = topOffset;
 2882          rcFormat.left += 2;  // Small left margin
 2883          rcFormat.right -= 2; // Small right margin
 2884  
 2885          SendMessage(hEdit, EM_SETRECT, 0, reinterpret_cast<LPARAM>(&rcFormat));
 2886      };
 2887  
 2888      switch (uMsg)
 2889      {
 2890      case WM_SIZE:
 2891      {
 2892          // Adjust the formatting rectangle to vertically center text
 2893          LRESULT result = DefSubclassProc(hWnd, uMsg, wParam, lParam);
 2894          AdjustTextRect(hWnd);
 2895          return result;
 2896      }
 2897  
 2898      case WM_SETFONT:
 2899      {
 2900          // After font is set, adjust formatting rectangle
 2901          LRESULT result = DefSubclassProc(hWnd, uMsg, wParam, lParam);
 2902          AdjustTextRect(hWnd);
 2903          return result;
 2904      }
 2905  
 2906      case WM_NCPAINT:
 2907          if (IsDarkModeEnabled())
 2908          {
 2909              OutputDebugStringW(L"[Edit] WM_NCPAINT in dark mode\n");
 2910  
 2911              // Get the window DC which includes NC area
 2912              HDC hdc = GetWindowDC(hWnd);
 2913              if (hdc)
 2914              {
 2915                  RECT rc;
 2916                  GetWindowRect(hWnd, &rc);
 2917                  int width = rc.right - rc.left;
 2918                  int height = rc.bottom - rc.top;
 2919                  rc.left = 0;
 2920                  rc.top = 0;
 2921                  rc.right = width;
 2922                  rc.bottom = height;
 2923  
 2924                  // Get NC border size
 2925                  RECT rcClient;
 2926                  GetClientRect(hWnd, &rcClient);
 2927                  MapWindowPoints(hWnd, nullptr, reinterpret_cast<LPPOINT>(&rcClient), 2);
 2928  
 2929                  RECT rcWindow;
 2930                  GetWindowRect(hWnd, &rcWindow);
 2931  
 2932                  int borderLeft = rcClient.left - rcWindow.left;
 2933                  int borderTop = rcClient.top - rcWindow.top;
 2934                  int borderRight = rcWindow.right - rcClient.right;
 2935                  int borderBottom = rcWindow.bottom - rcClient.bottom;
 2936  
 2937                  OutputDebugStringW((L"[Edit] Border: L=" + std::to_wstring(borderLeft) + L" T=" + std::to_wstring(borderTop) +
 2938                      L" R=" + std::to_wstring(borderRight) + L" B=" + std::to_wstring(borderBottom) + L"\n").c_str());
 2939  
 2940                  // Fill the entire NC border area with background color
 2941                  HBRUSH hBrush = CreateSolidBrush(DarkMode::BackgroundColor);
 2942  
 2943                  // Top border
 2944                  RECT rcTop = { 0, 0, width, borderTop };
 2945                  FillRect(hdc, &rcTop, hBrush);
 2946  
 2947                  // Bottom border
 2948                  RECT rcBottom = { 0, height - borderBottom, width, height };
 2949                  FillRect(hdc, &rcBottom, hBrush);
 2950  
 2951                  // Left border
 2952                  RECT rcLeft = { 0, borderTop, borderLeft, height - borderBottom };
 2953                  FillRect(hdc, &rcLeft, hBrush);
 2954  
 2955                  // Right border
 2956                  RECT rcRight = { width - borderRight, borderTop, width, height - borderBottom };
 2957                  FillRect(hdc, &rcRight, hBrush);
 2958  
 2959                  DeleteObject(hBrush);
 2960  
 2961                  // Draw thin border around the control
 2962                  HPEN hPen = CreatePen(PS_SOLID, 1, DarkMode::BorderColor);
 2963                  HPEN hOldPen = static_cast<HPEN>(SelectObject(hdc, hPen));
 2964                  HBRUSH hOldBrush = static_cast<HBRUSH>(SelectObject(hdc, GetStockObject(NULL_BRUSH)));
 2965                  Rectangle(hdc, 0, 0, width, height);
 2966                  SelectObject(hdc, hOldBrush);
 2967                  SelectObject(hdc, hOldPen);
 2968                  DeleteObject(hPen);
 2969  
 2970                  ReleaseDC(hWnd, hdc);
 2971              }
 2972              return 0;
 2973          }
 2974          break;
 2975  
 2976      case WM_NCDESTROY:
 2977          RemoveWindowSubclass(hWnd, EditControlSubclassProc, uIdSubclass);
 2978          break;
 2979      }
 2980      return DefSubclassProc(hWnd, uMsg, wParam, lParam);
 2981  }
 2982  
 2983  //----------------------------------------------------------------------------
 2984  //
 2985  // SliderSubclassProc
 2986  //
 2987  // Subclass procedure for slider/trackbar controls to handle dark mode
 2988  //
 2989  //----------------------------------------------------------------------------
 2990  LRESULT CALLBACK SliderSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
 2991  {
 2992      switch (uMsg)
 2993      {
 2994      case WM_LBUTTONDOWN:
 2995      case WM_MOUSEMOVE:
 2996      case WM_LBUTTONUP:
 2997          if (IsDarkModeEnabled())
 2998          {
 2999              // Let the default handler process the message first
 3000              LRESULT result = DefSubclassProc(hWnd, uMsg, wParam, lParam);
 3001              // Force full repaint to avoid artifacts at high DPI
 3002              InvalidateRect(hWnd, nullptr, TRUE);
 3003              return result;
 3004          }
 3005          break;
 3006  
 3007      case WM_PAINT:
 3008          if (IsDarkModeEnabled())
 3009          {
 3010              PAINTSTRUCT ps;
 3011              HDC hdc = BeginPaint(hWnd, &ps);
 3012  
 3013              RECT rc;
 3014              GetClientRect(hWnd, &rc);
 3015  
 3016              // Fill background
 3017              FillRect(hdc, &rc, GetDarkModeBrush());
 3018  
 3019              // Get slider info
 3020              RECT rcChannel = { 0 };
 3021              SendMessage(hWnd, TBM_GETCHANNELRECT, 0, reinterpret_cast<LPARAM>(&rcChannel));
 3022  
 3023              RECT rcThumb = { 0 };
 3024              SendMessage(hWnd, TBM_GETTHUMBRECT, 0, reinterpret_cast<LPARAM>(&rcThumb));
 3025  
 3026              // Draw channel (track) - simple dark line
 3027              int channelHeight = 4;
 3028              int channelY = (rc.bottom + rc.top) / 2 - channelHeight / 2;
 3029              RECT rcTrack = { rcChannel.left, channelY, rcChannel.right, channelY + channelHeight };
 3030              HBRUSH hTrackBrush = CreateSolidBrush(RGB(80, 80, 85));
 3031              FillRect(hdc, &rcTrack, hTrackBrush);
 3032              DeleteObject(hTrackBrush);
 3033  
 3034              // Center thumb vertically - at high DPI the thumb rect may not be centered
 3035              int thumbHeight = rcThumb.bottom - rcThumb.top;
 3036              int thumbCenterY = (rc.bottom + rc.top) / 2;
 3037              rcThumb.top = thumbCenterY - thumbHeight / 2;
 3038              rcThumb.bottom = rcThumb.top + thumbHeight;
 3039  
 3040              // Draw thumb - dark rectangle
 3041              HBRUSH hThumbBrush = CreateSolidBrush(RGB(160, 160, 165));
 3042              FillRect(hdc, &rcThumb, hThumbBrush);
 3043              DeleteObject(hThumbBrush);
 3044  
 3045              // Draw thumb border
 3046              HPEN hPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 105));
 3047              HPEN hOldPen = static_cast<HPEN>(SelectObject(hdc, hPen));
 3048              HBRUSH hOldBrush = static_cast<HBRUSH>(SelectObject(hdc, GetStockObject(NULL_BRUSH)));
 3049              Rectangle(hdc, rcThumb.left, rcThumb.top, rcThumb.right, rcThumb.bottom);
 3050              SelectObject(hdc, hOldBrush);
 3051              SelectObject(hdc, hOldPen);
 3052              DeleteObject(hPen);
 3053  
 3054              EndPaint(hWnd, &ps);
 3055              return 0;
 3056          }
 3057          break;
 3058  
 3059      case WM_ERASEBKGND:
 3060          if (IsDarkModeEnabled())
 3061          {
 3062              HDC hdc = reinterpret_cast<HDC>(wParam);
 3063              RECT rc;
 3064              GetClientRect(hWnd, &rc);
 3065              FillRect(hdc, &rc, GetDarkModeBrush());
 3066              return TRUE;
 3067          }
 3068          break;
 3069  
 3070      case WM_NCDESTROY:
 3071          RemoveWindowSubclass(hWnd, SliderSubclassProc, uIdSubclass);
 3072          break;
 3073      }
 3074      return DefSubclassProc(hWnd, uMsg, wParam, lParam);
 3075  }
 3076  
 3077  //----------------------------------------------------------------------------
 3078  //
 3079  // GroupBoxSubclassProc
 3080  //
 3081  // Subclass procedure for group box controls to handle dark mode painting
 3082  //
 3083  //----------------------------------------------------------------------------
 3084  LRESULT CALLBACK GroupBoxSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
 3085  {
 3086      switch (uMsg)
 3087      {
 3088      case WM_PAINT:
 3089          if (IsDarkModeEnabled())
 3090          {
 3091              PAINTSTRUCT ps;
 3092              HDC hdc = BeginPaint(hWnd, &ps);
 3093  
 3094              RECT rc;
 3095              GetClientRect(hWnd, &rc);
 3096  
 3097              // Get the text and font
 3098              HFONT hFont = reinterpret_cast<HFONT>(SendMessage(hWnd, WM_GETFONT, 0, 0));
 3099              HFONT hOldFont = hFont ? static_cast<HFONT>(SelectObject(hdc, hFont)) : nullptr;
 3100  
 3101              TCHAR szText[256] = { 0 };
 3102              GetWindowText(hWnd, szText, _countof(szText));
 3103  
 3104              // Measure text
 3105              SIZE textSize = { 0 };
 3106              GetTextExtentPoint32(hdc, szText, static_cast<int>(_tcslen(szText)), &textSize);
 3107  
 3108              // Text starts at left + 8 pixels
 3109              const int textLeft = 8;
 3110              const int textPadding = 4;
 3111              int frameTop = textSize.cy / 2;
 3112  
 3113              // Only fill the frame border areas, not the interior (to avoid painting over child controls)
 3114              // Fill top strip (above frame line)
 3115              RECT rcTop = { rc.left, rc.top, rc.right, frameTop + 1 };
 3116              FillRect(hdc, &rcTop, GetDarkModeBrush());
 3117  
 3118              // Fill left edge strip
 3119              RECT rcLeft = { rc.left, frameTop, rc.left + 1, rc.bottom };
 3120              FillRect(hdc, &rcLeft, GetDarkModeBrush());
 3121  
 3122              // Fill right edge strip
 3123              RECT rcRight = { rc.right - 1, frameTop, rc.right, rc.bottom };
 3124              FillRect(hdc, &rcRight, GetDarkModeBrush());
 3125  
 3126              // Fill bottom edge strip
 3127              RECT rcBottom = { rc.left, rc.bottom - 1, rc.right, rc.bottom };
 3128              FillRect(hdc, &rcBottom, GetDarkModeBrush());
 3129  
 3130              // Draw the group box frame (with gap for text)
 3131              HPEN hPen = CreatePen(PS_SOLID, 1, DarkMode::BorderColor);
 3132              HPEN hOldPen = static_cast<HPEN>(SelectObject(hdc, hPen));
 3133  
 3134              // Top line - left segment (before text)
 3135              MoveToEx(hdc, rc.left, frameTop, NULL);
 3136              LineTo(hdc, textLeft - textPadding, frameTop);
 3137  
 3138              // Top line - right segment (after text)
 3139              MoveToEx(hdc, textLeft + textSize.cx + textPadding, frameTop, NULL);
 3140              LineTo(hdc, rc.right - 1, frameTop);
 3141  
 3142              // Right line
 3143              LineTo(hdc, rc.right - 1, rc.bottom - 1);
 3144  
 3145              // Bottom line
 3146              LineTo(hdc, rc.left, rc.bottom - 1);
 3147  
 3148              // Left line
 3149              LineTo(hdc, rc.left, frameTop);
 3150  
 3151              SelectObject(hdc, hOldPen);
 3152              DeleteObject(hPen);
 3153  
 3154              // Draw text with background
 3155              SetBkMode(hdc, OPAQUE);
 3156              SetBkColor(hdc, DarkMode::BackgroundColor);
 3157              SetTextColor(hdc, DarkMode::TextColor);
 3158              RECT rcText = { textLeft, 0, textLeft + textSize.cx, textSize.cy };
 3159              DrawText(hdc, szText, -1, &rcText, DT_LEFT | DT_SINGLELINE);
 3160  
 3161              if (hOldFont)
 3162                  SelectObject(hdc, hOldFont);
 3163  
 3164              EndPaint(hWnd, &ps);
 3165              return 0;
 3166          }
 3167          break;
 3168  
 3169      case WM_ERASEBKGND:
 3170          // Don't erase background - let parent handle it
 3171          if (IsDarkModeEnabled())
 3172          {
 3173              return TRUE;
 3174          }
 3175          break;
 3176  
 3177      case WM_NCDESTROY:
 3178          RemoveWindowSubclass(hWnd, GroupBoxSubclassProc, uIdSubclass);
 3179          break;
 3180      }
 3181      return DefSubclassProc(hWnd, uMsg, wParam, lParam);
 3182  }
 3183  
 3184  //----------------------------------------------------------------------------
 3185  //
 3186  // StaticTextSubclassProc
 3187  //
 3188  // Subclass procedure for static text controls to handle dark mode painting
 3189  //
 3190  //----------------------------------------------------------------------------
 3191  LRESULT CALLBACK StaticTextSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
 3192  {
 3193      const int ctrlId = GetDlgCtrlID( hWnd );
 3194      const bool isOptionsHeader = (ctrlId == IDC_VERSION || ctrlId == IDC_COPYRIGHT);
 3195  
 3196      auto paintStaticText = [](HWND hWnd, HDC hdc) -> void
 3197      {
 3198          RECT rc;
 3199          GetClientRect(hWnd, &rc);
 3200  
 3201          // Fill background
 3202          if( IsDarkModeEnabled() )
 3203          {
 3204              FillRect(hdc, &rc, GetDarkModeBrush());
 3205          }
 3206          else
 3207          {
 3208              FillRect(hdc, &rc, GetSysColorBrush( COLOR_BTNFACE ));
 3209          }
 3210  
 3211          // Get text
 3212          TCHAR text[512] = { 0 };
 3213          GetWindowText(hWnd, text, _countof(text));
 3214  
 3215          // Set up text drawing
 3216          SetBkMode(hdc, TRANSPARENT);
 3217          bool isEnabled = IsWindowEnabled(hWnd);
 3218          if( IsDarkModeEnabled() )
 3219          {
 3220              SetTextColor(hdc, isEnabled ? DarkMode::TextColor : RGB(100, 100, 100));
 3221          }
 3222          else
 3223          {
 3224              SetTextColor( hdc, isEnabled ? GetSysColor( COLOR_WINDOWTEXT ) : GetSysColor( COLOR_GRAYTEXT ) );
 3225          }
 3226  
 3227          // Try to get the font from a window property first (for header controls where
 3228          // WM_GETFONT may not work reliably), then fall back to WM_GETFONT.
 3229          HFONT hFont = static_cast<HFONT>(GetPropW( hWnd, L"ZoomIt.HeaderFont" ));
 3230          HFONT hCreatedFont = nullptr; // Track if we created a font that needs cleanup
 3231  
 3232          // For IDC_VERSION, create a large title font on-demand if the property font doesn't work
 3233          const int thisCtrlId = GetDlgCtrlID( hWnd );
 3234          if( thisCtrlId == IDC_VERSION )
 3235          {
 3236              // Create a title font that is proportionally larger than the dialog font
 3237              LOGFONT lf{};
 3238              HFONT hDialogFont = reinterpret_cast<HFONT>(SendMessage( GetParent( hWnd ), WM_GETFONT, 0, 0 ));
 3239              if( hDialogFont )
 3240              {
 3241                  GetObject( hDialogFont, sizeof( lf ), &lf );
 3242              }
 3243              else
 3244              {
 3245                  GetObject( GetStockObject( DEFAULT_GUI_FONT ), sizeof( lf ), &lf );
 3246              }
 3247              lf.lfWeight = FW_BOLD;
 3248              // Make title 50% larger than dialog font (lfHeight is negative for character height)
 3249              lf.lfHeight = MulDiv( lf.lfHeight, 3, 2 );
 3250              hCreatedFont = CreateFontIndirect( &lf );
 3251              if( hCreatedFont )
 3252              {
 3253                  hFont = hCreatedFont;
 3254              }
 3255          }
 3256  
 3257          if( !hFont )
 3258          {
 3259              hFont = reinterpret_cast<HFONT>(SendMessage(hWnd, WM_GETFONT, 0, 0));
 3260          }
 3261          HFONT hOldFont = nullptr;
 3262          if (hFont)
 3263          {
 3264              hOldFont = static_cast<HFONT>(SelectObject(hdc, hFont));
 3265          }
 3266  
 3267  #if _DEBUG
 3268          if( thisCtrlId == IDC_VERSION )
 3269          {
 3270              TEXTMETRIC tm{};
 3271              GetTextMetrics( hdc, &tm );
 3272              OutputDebug(L"IDC_VERSION paint: tmHeight=%d selectResult=%p hFont=%p created=%p rc=(%d,%d,%d,%d)\n",
 3273                  tm.tmHeight, hOldFont, hFont, hCreatedFont, rc.left, rc.top, rc.right, rc.bottom );
 3274          }
 3275  #endif
 3276  
 3277          // Get style to determine alignment and wrapping behavior
 3278          LONG style = GetWindowLong(hWnd, GWL_STYLE);
 3279          const LONG staticType = style & SS_TYPEMASK;
 3280  
 3281          UINT format = 0;
 3282          if (style & SS_CENTER)
 3283              format |= DT_CENTER;
 3284          else if (style & SS_RIGHT)
 3285              format |= DT_RIGHT;
 3286          else
 3287              format |= DT_LEFT;
 3288  
 3289          if (style & SS_NOPREFIX)
 3290              format |= DT_NOPREFIX;
 3291  
 3292          bool noWrap = (staticType == SS_LEFTNOWORDWRAP) || (staticType == SS_SIMPLE);
 3293          if( GetDlgCtrlID( hWnd ) == IDC_VERSION )
 3294          {
 3295              // The header title is intended to be a single line.
 3296              noWrap = true;
 3297          }
 3298          if (noWrap)
 3299          {
 3300              // Single-line labels should match the classic static control behavior.
 3301              format |= DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS;
 3302          }
 3303          else
 3304          {
 3305              // Multi-line/static text (LTEXT) should wrap like the default control.
 3306              format |= DT_WORDBREAK | DT_EDITCONTROL;
 3307          }
 3308  
 3309          DrawText(hdc, text, -1, &rc, format);
 3310  
 3311          if (hOldFont)
 3312          {
 3313              SelectObject(hdc, hOldFont);
 3314          }
 3315  
 3316          // Clean up any font we created on-demand
 3317          if( hCreatedFont )
 3318          {
 3319              DeleteObject( hCreatedFont );
 3320          }
 3321      };
 3322  
 3323      if (IsDarkModeEnabled() || isOptionsHeader)
 3324      {
 3325          switch (uMsg)
 3326          {
 3327          case WM_ERASEBKGND:
 3328              {
 3329                  HDC hdc = reinterpret_cast<HDC>(wParam);
 3330                  RECT rc;
 3331                  GetClientRect(hWnd, &rc);
 3332                  if( IsDarkModeEnabled() )
 3333                  {
 3334                      FillRect(hdc, &rc, GetDarkModeBrush());
 3335                  }
 3336                  else
 3337                  {
 3338                      FillRect(hdc, &rc, GetSysColorBrush( COLOR_BTNFACE ));
 3339                  }
 3340                  return TRUE;
 3341              }
 3342  
 3343          case WM_PAINT:
 3344              {
 3345                  PAINTSTRUCT ps;
 3346                  HDC hdc = BeginPaint(hWnd, &ps);
 3347                  paintStaticText(hWnd, hdc);
 3348                  EndPaint(hWnd, &ps);
 3349                  return 0;
 3350              }
 3351  
 3352          case WM_PRINTCLIENT:
 3353              {
 3354                  HDC hdc = reinterpret_cast<HDC>(wParam);
 3355                  paintStaticText(hWnd, hdc);
 3356                  return 0;
 3357              }
 3358  
 3359          case WM_SETTEXT:
 3360              {
 3361                  // Let the default handle the text change, then repaint
 3362                  DefWindowProc(hWnd, uMsg, wParam, lParam);
 3363                  InvalidateRect(hWnd, nullptr, TRUE);
 3364                  return TRUE;
 3365              }
 3366  
 3367          case WM_ENABLE:
 3368              {
 3369                  // Let base window proc handle enable state change, but avoid any subclass chain
 3370                  // that might trigger themed drawing
 3371                  LRESULT result = DefWindowProc(hWnd, uMsg, wParam, lParam);
 3372                  // Force immediate repaint with our custom painting
 3373                  InvalidateRect(hWnd, nullptr, TRUE);
 3374                  UpdateWindow(hWnd);
 3375                  return result;
 3376              }
 3377  
 3378          case WM_NCDESTROY:
 3379  #if _DEBUG
 3380              RemovePropW( hWnd, L"ZoomIt.VersionFontLogged" );
 3381  #endif
 3382              RemoveWindowSubclass(hWnd, StaticTextSubclassProc, uIdSubclass);
 3383              break;
 3384          }
 3385      }
 3386      else
 3387      {
 3388          if (uMsg == WM_NCDESTROY)
 3389          {
 3390  #if _DEBUG
 3391              RemovePropW( hWnd, L"ZoomIt.VersionFontLogged" );
 3392  #endif
 3393              RemoveWindowSubclass(hWnd, StaticTextSubclassProc, uIdSubclass);
 3394          }
 3395      }
 3396      return DefSubclassProc(hWnd, uMsg, wParam, lParam);
 3397  }
 3398  
 3399  
 3400  
 3401  //----------------------------------------------------------------------------
 3402  //
 3403  // TabControlSubclassProc
 3404  //
 3405  // Subclass procedure for tab control to handle dark mode background
 3406  //
 3407  //----------------------------------------------------------------------------
 3408  LRESULT CALLBACK TabControlSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
 3409  {
 3410      switch (uMsg)
 3411      {
 3412      case WM_ERASEBKGND:
 3413          if (IsDarkModeEnabled())
 3414          {
 3415              HDC hdc = reinterpret_cast<HDC>(wParam);
 3416              RECT rc;
 3417              GetClientRect(hWnd, &rc);
 3418              FillRect(hdc, &rc, GetDarkModeBrush());
 3419              return TRUE;
 3420          }
 3421          break;
 3422  
 3423      case WM_PAINT:
 3424          if (IsDarkModeEnabled())
 3425          {
 3426              PAINTSTRUCT ps;
 3427              HDC hdc = BeginPaint(hWnd, &ps);
 3428  
 3429              RECT rcClient;
 3430              GetClientRect(hWnd, &rcClient);
 3431  
 3432              // Fill entire background with dark color
 3433              FillRect(hdc, &rcClient, GetDarkModeBrush());
 3434  
 3435              // Get the display area (content area below tabs)
 3436              RECT rcDisplay = rcClient;
 3437              TabCtrl_AdjustRect(hWnd, FALSE, &rcDisplay);
 3438  
 3439              // Debug output
 3440              TCHAR dbg[256];
 3441              _stprintf_s(dbg, _T("TabCtrl: client=(%d,%d,%d,%d) display=(%d,%d,%d,%d)\n"),
 3442                  rcClient.left, rcClient.top, rcClient.right, rcClient.bottom,
 3443                  rcDisplay.left, rcDisplay.top, rcDisplay.right, rcDisplay.bottom);
 3444              OutputDebugString(dbg);
 3445  
 3446              // Draw grey border around the display area
 3447              HPEN hPen = CreatePen(PS_SOLID, 1, DarkMode::BorderColor);
 3448              HPEN hOldPen = static_cast<HPEN>(SelectObject(hdc, hPen));
 3449              HBRUSH hOldBrush = static_cast<HBRUSH>(SelectObject(hdc, GetStockObject(NULL_BRUSH)));
 3450  
 3451              // Draw border at the edges of the display area (inset by 1 to be visible)
 3452              int left = rcDisplay.left;
 3453              int top = rcDisplay.top - 1;
 3454              int right = (rcDisplay.right < rcClient.right) ? rcDisplay.right : rcClient.right - 1;
 3455              int bottom = (rcDisplay.bottom < rcClient.bottom) ? rcDisplay.bottom : rcClient.bottom - 1;
 3456  
 3457              _stprintf_s(dbg, _T("TabCtrl border: left=%d top=%d right=%d bottom=%d\n"), left, top, right, bottom);
 3458              OutputDebugString(dbg);
 3459  
 3460              // Top line
 3461              MoveToEx(hdc, left, top, NULL);
 3462              LineTo(hdc, right, top);
 3463              // Right line
 3464              MoveToEx(hdc, right - 1, top, NULL);
 3465              LineTo(hdc, right - 1, bottom);
 3466              // Bottom line
 3467              MoveToEx(hdc, left, bottom - 1, NULL);
 3468              LineTo(hdc, right, bottom - 1);
 3469              // Left line
 3470              MoveToEx(hdc, left, top, NULL);
 3471              LineTo(hdc, left, bottom);
 3472  
 3473              // Draw each tab
 3474              int tabCount = TabCtrl_GetItemCount(hWnd);
 3475              int selectedTab = TabCtrl_GetCurSel(hWnd);
 3476  
 3477              // Get the font from the tab control
 3478              HFONT hFont = reinterpret_cast<HFONT>(SendMessage(hWnd, WM_GETFONT, 0, 0));
 3479              HFONT hOldFont = hFont ? static_cast<HFONT>(SelectObject(hdc, hFont)) : nullptr;
 3480  
 3481              SetBkMode(hdc, TRANSPARENT);
 3482  
 3483              for (int i = 0; i < tabCount; i++)
 3484              {
 3485                  RECT rcTab;
 3486                  TabCtrl_GetItemRect(hWnd, i, &rcTab);
 3487  
 3488                  bool isSelected = (i == selectedTab);
 3489  
 3490                  // Fill tab background
 3491                  FillRect(hdc, &rcTab, GetDarkModeBrush());
 3492  
 3493                  // Draw grey border around tab (left, top, right)
 3494                  MoveToEx(hdc, rcTab.left, rcTab.bottom - 1, NULL);
 3495                  LineTo(hdc, rcTab.left, rcTab.top);
 3496                  LineTo(hdc, rcTab.right - 1, rcTab.top);
 3497                  LineTo(hdc, rcTab.right - 1, rcTab.bottom);
 3498  
 3499                  // For selected tab, erase the bottom border to merge with content
 3500                  if (isSelected)
 3501                  {
 3502                      HPEN hBgPen = CreatePen(PS_SOLID, 1, DarkMode::BackgroundColor);
 3503                      SelectObject(hdc, hBgPen);
 3504                      MoveToEx(hdc, rcTab.left + 1, rcTab.bottom - 1, NULL);
 3505                      LineTo(hdc, rcTab.right - 1, rcTab.bottom - 1);
 3506                      SelectObject(hdc, hPen);
 3507                      DeleteObject(hBgPen);
 3508                  }
 3509  
 3510                  // Get tab text
 3511                  TCITEM tci = {};
 3512                  tci.mask = TCIF_TEXT;
 3513                  TCHAR szText[128] = { 0 };
 3514                  tci.pszText = szText;
 3515                  tci.cchTextMax = _countof(szText);
 3516                  TabCtrl_GetItem(hWnd, i, &tci);
 3517  
 3518                  // Draw text
 3519                  SetTextColor(hdc, isSelected ? DarkMode::TextColor : DarkMode::DisabledTextColor);
 3520                  RECT rcText = rcTab;
 3521                  rcText.top += 4;
 3522                  DrawText(hdc, szText, -1, &rcText, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
 3523  
 3524                  // Draw underline for selected tab
 3525                  if (isSelected)
 3526                  {
 3527                      RECT rcUnderline = rcTab;
 3528                      rcUnderline.top = rcUnderline.bottom - 2;
 3529                      rcUnderline.left += 1;
 3530                      rcUnderline.right -= 1;
 3531                      HBRUSH hAccent = CreateSolidBrush(DarkMode::AccentColor);
 3532                      FillRect(hdc, &rcUnderline, hAccent);
 3533                      DeleteObject(hAccent);
 3534                  }
 3535              }
 3536  
 3537              if (hOldFont)
 3538                  SelectObject(hdc, hOldFont);
 3539              SelectObject(hdc, hOldBrush);
 3540              SelectObject(hdc, hOldPen);
 3541              DeleteObject(hPen);
 3542  
 3543              EndPaint(hWnd, &ps);
 3544              return 0;
 3545          }
 3546          break;
 3547  
 3548      case WM_NCDESTROY:
 3549          RemoveWindowSubclass(hWnd, TabControlSubclassProc, uIdSubclass);
 3550          break;
 3551      }
 3552      return DefSubclassProc(hWnd, uMsg, wParam, lParam);
 3553  }
 3554  
 3555  //----------------------------------------------------------------------------
 3556  //
 3557  // OptionsProc
 3558  //
 3559  //----------------------------------------------------------------------------
 3560  INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
 3561                               WPARAM wParam, LPARAM lParam )
 3562  {
 3563      constexpr UINT WM_APPLY_HEADER_FONTS = WM_APP + 42;
 3564      static HFONT	hFontBold = nullptr;
 3565      static HFONT    hFontVersion = nullptr;
 3566      PNMLINK			notify = nullptr;
 3567      static int		curTabSel = 0;
 3568      static HWND		hTabCtrl;
 3569      static HWND		hOpacity;
 3570      static HWND		hToggleKey;
 3571      static UINT		currentDpi = DPI_BASELINE;
 3572      static RECT     stableWindowRect{};
 3573      static bool     stableWindowRectValid = false;
 3574      TCHAR			text[32];
 3575      DWORD			newToggleKey, newTimeout, newToggleMod, newBreakToggleKey, newDemoTypeToggleKey, newRecordToggleKey, newSnipToggleKey;
 3576      DWORD			newDrawToggleKey, newDrawToggleMod, newBreakToggleMod, newDemoTypeToggleMod, newRecordToggleMod, newSnipToggleMod;
 3577      DWORD			newLiveZoomToggleKey, newLiveZoomToggleMod;
 3578      static std::vector<std::pair<std::wstring, std::wstring>>	microphones;
 3579  
 3580      auto CleanupFonts = [&]()
 3581      {
 3582          if( hFontBold )
 3583          {
 3584              DeleteObject( hFontBold );
 3585              hFontBold = nullptr;
 3586          }
 3587          if( hFontVersion )
 3588          {
 3589              DeleteObject( hFontVersion );
 3590              hFontVersion = nullptr;
 3591          }
 3592      };
 3593  
 3594      auto UpdateVersionFont = [&]()
 3595      {
 3596          if( hFontVersion )
 3597          {
 3598              DeleteObject( hFontVersion );
 3599              hFontVersion = nullptr;
 3600          }
 3601  
 3602          HWND hVersion = GetDlgItem( hDlg, IDC_VERSION );
 3603          if( !hVersion )
 3604          {
 3605              return;
 3606          }
 3607  
 3608          // Prefer the control's current font (it may already be DPI-scaled).
 3609          HFONT hBaseFont = reinterpret_cast<HFONT>(SendMessage( hVersion, WM_GETFONT, 0, 0 ));
 3610          if( !hBaseFont )
 3611          {
 3612              hBaseFont = reinterpret_cast<HFONT>(SendMessage( hDlg, WM_GETFONT, 0, 0 ));
 3613          }
 3614          if( !hBaseFont )
 3615          {
 3616              hBaseFont = static_cast<HFONT>(GetStockObject( DEFAULT_GUI_FONT ));
 3617          }
 3618  
 3619          LOGFONT lf{};
 3620          if( !GetObject( hBaseFont, sizeof( lf ), &lf ) )
 3621          {
 3622              return;
 3623          }
 3624  
 3625          // Make the header version text title-sized using an explicit point size,
 3626          // scaled by the current DPI.
 3627          const UINT dpi = GetDpiForWindowHelper( hDlg );
 3628          constexpr int kTitlePointSize = 22;
 3629  
 3630          lf.lfWeight = FW_BOLD;
 3631          lf.lfHeight = -MulDiv( kTitlePointSize, static_cast<int>(dpi), 72 );
 3632          hFontVersion = CreateFontIndirect( &lf );
 3633          if( hFontVersion )
 3634          {
 3635              SendMessage( hVersion, WM_SETFONT, reinterpret_cast<WPARAM>(hFontVersion), TRUE );
 3636              // Also store in a property so our subclass paint can reliably retrieve it.
 3637              SetPropW( hVersion, L"ZoomIt.HeaderFont", reinterpret_cast<HANDLE>(hFontVersion) );
 3638  #if _DEBUG
 3639              HFONT checkFont = static_cast<HFONT>(GetPropW( hVersion, L"ZoomIt.HeaderFont" ));
 3640              OutputDebug( L"SetPropW HeaderFont: hwnd=%p font=%p verify=%p\n", hVersion, hFontVersion, checkFont );
 3641  #endif
 3642          }
 3643  
 3644      #if _DEBUG
 3645          OutputDebug(L"UpdateVersionFont: dpi=%u titlePt=%d lfHeight=%d font=%p\n",
 3646              dpi, kTitlePointSize, lf.lfHeight, hFontVersion );
 3647  
 3648          {
 3649              HFONT currentFont = reinterpret_cast<HFONT>(SendMessage( hVersion, WM_GETFONT, 0, 0 ));
 3650              LOGFONT currentLf{};
 3651              if( currentFont && GetObject( currentFont, sizeof( currentLf ), &currentLf ) )
 3652              {
 3653                  OutputDebug( L"IDC_VERSION WM_GETFONT after set: font=%p lfHeight=%d lfWeight=%d\n",
 3654                      currentFont, currentLf.lfHeight, currentLf.lfWeight );
 3655              }
 3656              else
 3657              {
 3658                  OutputDebug( L"IDC_VERSION WM_GETFONT after set: font=%p (no logfont)\n", currentFont );
 3659              }
 3660          }
 3661      #endif
 3662  
 3663          // Resize the version control to fit the new font, and reflow the lines below if needed.
 3664          RECT rcVersion{};
 3665          GetWindowRect( hVersion, &rcVersion );
 3666          MapWindowPoints( nullptr, hDlg, reinterpret_cast<LPPOINT>(&rcVersion), 2 );
 3667          const int oldVersionHeight = rcVersion.bottom - rcVersion.top;
 3668  
 3669          TCHAR versionText[128] = {};
 3670          GetWindowText( hVersion, versionText, _countof( versionText ) );
 3671  
 3672          RECT rcCalc{ 0, 0, 0, 0 };
 3673          HDC hdc = GetDC( hVersion );
 3674          if( hdc )
 3675          {
 3676              HFONT oldFont = static_cast<HFONT>(SelectObject( hdc, hFontVersion ? hFontVersion : hBaseFont ));
 3677              DrawText( hdc, versionText, -1, &rcCalc, DT_CALCRECT | DT_SINGLELINE | DT_LEFT | DT_VCENTER );
 3678              SelectObject( hdc, oldFont );
 3679              ReleaseDC( hVersion, hdc );
 3680          }
 3681  
 3682          // Keep within dialog client width.
 3683          RECT rcClient{};
 3684          GetClientRect( hDlg, &rcClient );
 3685          const int maxWidth = max( 0, rcClient.right - rcVersion.left - ScaleForDpi( 8, GetDpiForWindowHelper( hDlg ) ) );
 3686          const int desiredWidth = min( maxWidth, (rcCalc.right - rcCalc.left) + ScaleForDpi( 6, GetDpiForWindowHelper( hDlg ) ) );
 3687          const int desiredHeight = (rcCalc.bottom - rcCalc.top) + ScaleForDpi( 2, GetDpiForWindowHelper( hDlg ) );
 3688          const int newVersionHeight = max( oldVersionHeight, desiredHeight );
 3689  
 3690          SetWindowPos( hVersion, nullptr,
 3691              rcVersion.left, rcVersion.top,
 3692              max( 1, desiredWidth ), newVersionHeight,
 3693              SWP_NOZORDER | SWP_NOACTIVATE );
 3694  
 3695  #if _DEBUG
 3696          {
 3697              RECT rcAfter{};
 3698              GetClientRect( hVersion, &rcAfter );
 3699              OutputDebug( L"UpdateVersionFont resize: desired=(%d,%d) oldH=%d newH=%d actual=(%d,%d)\n",
 3700                  desiredWidth, desiredHeight, oldVersionHeight, newVersionHeight,
 3701                  rcAfter.right - rcAfter.left, rcAfter.bottom - rcAfter.top );
 3702          }
 3703  #endif
 3704  
 3705          InvalidateRect( hVersion, nullptr, TRUE );
 3706  
 3707          const int deltaY = newVersionHeight - oldVersionHeight;
 3708          if( deltaY > 0 )
 3709          {
 3710              const int headerIdsToShift[] = { IDC_COPYRIGHT, IDC_LINK };
 3711              for( int i = 0; i < _countof( headerIdsToShift ); i++ )
 3712              {
 3713                  HWND hCtrl = GetDlgItem( hDlg, headerIdsToShift[i] );
 3714                  if( !hCtrl )
 3715                  {
 3716                      continue;
 3717                  }
 3718                  RECT rc{};
 3719                  GetWindowRect( hCtrl, &rc );
 3720                  MapWindowPoints( nullptr, hDlg, reinterpret_cast<LPPOINT>(&rc), 2 );
 3721                  SetWindowPos( hCtrl, nullptr,
 3722                      rc.left, rc.top + deltaY,
 3723                      0, 0,
 3724                      SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE );
 3725              }
 3726          }
 3727      };
 3728  
 3729      switch ( message )  {
 3730      case WM_INITDIALOG:
 3731      {
 3732          if( hWndOptions ) {
 3733  
 3734              BringWindowToTop( hWndOptions );
 3735              SetFocus( hWndOptions );
 3736              SetForegroundWindow( hWndOptions );
 3737              EndDialog( hDlg, 0 );
 3738              return FALSE;
 3739          }
 3740          hWndOptions = hDlg;
 3741  
 3742          // Set the dialog icon
 3743          {
 3744              HICON hIcon = LoadIcon( g_hInstance, L"APPICON" );
 3745              if( hIcon )
 3746              {
 3747                  SendMessage( hDlg, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIcon) );
 3748                  SendMessage( hDlg, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(hIcon) );
 3749              }
 3750          }
 3751  
 3752          SetForegroundWindow( hDlg );
 3753          SetActiveWindow( hDlg );
 3754          // Do not force-show the dialog here. DialogBox will show it after WM_INITDIALOG
 3755          // returns, and showing early causes visible layout churn while we add tabs, scale,
 3756          // and center the window.
 3757  #if 1
 3758          // set version info
 3759          TCHAR               filePath[MAX_PATH];
 3760          const TCHAR* verString;
 3761  
 3762          GetModuleFileName(NULL, filePath, _countof(filePath));
 3763          DWORD               zero = 0;
 3764          DWORD               infoSize = GetFileVersionInfoSize(filePath, &zero);
 3765          void* versionInfo = malloc(infoSize);
 3766          GetFileVersionInfo(filePath, 0, infoSize, versionInfo);
 3767  
 3768          verString = GetVersionString(static_cast<VERSION_INFO*>(versionInfo), _T("FileVersion"));
 3769          SetDlgItemText(hDlg, IDC_VERSION, (std::wstring(L"ZoomIt v") + verString).c_str());
 3770  
 3771          verString = GetVersionString(static_cast<VERSION_INFO*>(versionInfo), _T("LegalCopyright"));
 3772          SetDlgItemText(hDlg, IDC_COPYRIGHT, verString);
 3773  
 3774          free(versionInfo);
 3775  #endif
 3776          // Add tabs
 3777          hTabCtrl = GetDlgItem( hDlg, IDC_TAB );
 3778  
 3779          // Set owner-draw style for tab control when in dark mode
 3780          if (IsDarkModeEnabled())
 3781          {
 3782              LONG_PTR style = GetWindowLongPtr(hTabCtrl, GWL_STYLE);
 3783              SetWindowLongPtr(hTabCtrl, GWL_STYLE, style | TCS_OWNERDRAWFIXED);
 3784              // Subclass the tab control for dark mode background painting
 3785              SetWindowSubclass(hTabCtrl, TabControlSubclassProc, 1, 0);
 3786          }
 3787  
 3788          OptionsAddTabs( hDlg, hTabCtrl );
 3789  
 3790          // Configure options
 3791          SendMessage( GetDlgItem( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_HOTKEY), HKM_SETRULES,
 3792              static_cast<WPARAM>(HKCOMB_NONE), // invalid key combinations
 3793              MAKELPARAM(HOTKEYF_ALT, 0));     // add ALT to invalid entries
 3794  
 3795          if( g_ToggleKey )		SendMessage( GetDlgItem( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_HOTKEY), HKM_SETHOTKEY, g_ToggleKey, 0 );
 3796          if( pMagInitialize ) {
 3797  
 3798              if( g_LiveZoomToggleKey )	SendMessage( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_LIVE_HOTKEY), HKM_SETHOTKEY, g_LiveZoomToggleKey, 0 );
 3799  
 3800          } else {
 3801  
 3802              EnableWindow( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_LIVE_HOTKEY), FALSE );
 3803              EnableWindow( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_ZOOM_LEVEL), FALSE );
 3804              EnableWindow( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_ZOOM_SPIN), FALSE );
 3805          }
 3806          if( g_DrawToggleKey )	SendMessage( GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_DRAW_HOTKEY), HKM_SETHOTKEY, g_DrawToggleKey, 0 );
 3807          if( g_BreakToggleKey )	SendMessage( GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_BREAK_HOTKEY), HKM_SETHOTKEY, g_BreakToggleKey, 0 );
 3808          if( g_DemoTypeToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_HOTKEY ), HKM_SETHOTKEY, g_DemoTypeToggleKey, 0 );
 3809          if( g_RecordToggleKey )	SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_HOTKEY), HKM_SETHOTKEY, g_RecordToggleKey, 0 );
 3810          if( g_SnipToggleKey) 	SendMessage( GetDlgItem( g_OptionsTabs[SNIP_PAGE].hPage, IDC_SNIP_HOTKEY), HKM_SETHOTKEY, g_SnipToggleKey, 0 );
 3811          CheckDlgButton( hDlg, IDC_SHOW_TRAY_ICON,
 3812              g_ShowTrayIcon ? BST_CHECKED: BST_UNCHECKED );
 3813          CheckDlgButton( hDlg, IDC_AUTOSTART,
 3814              IsAutostartConfigured() ? BST_CHECKED: BST_UNCHECKED );
 3815          CheckDlgButton( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ANIMATE_ZOOM,
 3816              g_AnimateZoom ? BST_CHECKED: BST_UNCHECKED );
 3817          CheckDlgButton( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_SMOOTH_IMAGE,
 3818              g_SmoothImage ? BST_CHECKED: BST_UNCHECKED );
 3819  
 3820          SendMessage( GetDlgItem(g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ZOOM_SLIDER), TBM_SETRANGE, false, MAKELONG(0,_countof(g_ZoomLevels)-1) );
 3821          SendMessage( GetDlgItem(g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ZOOM_SLIDER), TBM_SETPOS, true, g_SliderZoomLevel );
 3822  
 3823          _stprintf( text, L"%d", g_PenWidth );
 3824          SetDlgItemText( g_OptionsTabs[DRAW_PAGE].hPage, IDC_PEN_WIDTH, text );
 3825          SendMessage( GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_PEN_WIDTH ), EM_LIMITTEXT, 1, 0 );
 3826          SendMessage (GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_SPIN), UDM_SETRANGE, 0L,
 3827                              MAKELPARAM (19, 1));
 3828  
 3829          _stprintf( text, L"%d", g_BreakTimeout );
 3830          SetDlgItemText( g_OptionsTabs[BREAK_PAGE].hPage, IDC_TIMER, text );
 3831          SendMessage( GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_TIMER ), EM_LIMITTEXT, 2, 0 );
 3832          SendMessage (GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_SPIN_TIMER), UDM_SETRANGE, 0L,
 3833                              MAKELPARAM (99, 1));
 3834          CheckDlgButton( g_OptionsTabs[BREAK_PAGE].hPage, IDC_CHECK_SHOW_EXPIRED,
 3835              g_ShowExpiredTime ? BST_CHECKED : BST_UNCHECKED );
 3836  
 3837          CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_SYSTEM_AUDIO,
 3838              g_CaptureSystemAudio ? BST_CHECKED: BST_UNCHECKED );
 3839  
 3840          CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO,
 3841              g_CaptureAudio ? BST_CHECKED: BST_UNCHECKED );
 3842  
 3843          //
 3844          // The framerate drop down list is not used in the current version (might be added in the future)
 3845          //
 3846          /*for (int i = 0; i < _countof(g_FramerateOptions); i++) {
 3847  
 3848              _stprintf(text, L"%d", g_FramerateOptions[i]);
 3849              SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), static_cast<UINT>(CB_ADDSTRING),
 3850                  static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(text));
 3851              if (g_RecordFrameRate == g_FramerateOptions[i]) {
 3852  
 3853                  SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), CB_SETCURSEL, static_cast<WPARAM>(i), static_cast<LPARAM>(0));
 3854              }
 3855          }*/
 3856  
 3857          // Add the recording format to the combo box and set the current selection
 3858          size_t selection = 0;
 3859          const wchar_t* currentFormatString = (g_RecordingFormat == RecordingFormat::GIF) ? L"GIF" : L"MP4";
 3860  
 3861          for( size_t i = 0; i < (sizeof(g_RecordingFormats) / sizeof(g_RecordingFormats[0])); i++ )
 3862          {
 3863              SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FORMAT ), static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(g_RecordingFormats[i]) );
 3864  
 3865              if( selection == 0 && wcscmp( g_RecordingFormats[i], currentFormatString ) == 0 )
 3866              {
 3867                  selection = i;
 3868              }
 3869          }
 3870          SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FORMAT ), CB_SETCURSEL, static_cast<WPARAM>(selection), static_cast<LPARAM>(0) );
 3871  
 3872          for(unsigned int i = 1; i < 11; i++) {
 3873  
 3874              _stprintf(text, L"%2.1f", (static_cast<double>(i)) / 10 );
 3875              SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), static_cast<UINT>(CB_ADDSTRING),
 3876                  static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(text));
 3877  
 3878              if (g_RecordingFormat == RecordingFormat::GIF && i*10 == g_RecordScalingGIF ) {
 3879  
 3880                  SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), CB_SETCURSEL, static_cast<WPARAM>(i)-1, static_cast<LPARAM>(0));
 3881              }
 3882              if (g_RecordingFormat == RecordingFormat::MP4 && i*10 == g_RecordScalingMP4 ) {
 3883  
 3884                  SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), CB_SETCURSEL, static_cast<WPARAM>(i)-1, static_cast<LPARAM>(0));
 3885              }
 3886          }
 3887  
 3888          // Get the current set of microphones
 3889          microphones.clear();
 3890          concurrency::create_task([]{
 3891              auto devices = winrt::DeviceInformation::FindAllAsync( winrt::DeviceClass::AudioCapture ).get();
 3892              for( auto device : devices )
 3893              {
 3894                  microphones.emplace_back( device.Id().c_str(), device.Name().c_str() );
 3895              }
 3896          }).get();
 3897  
 3898          // Add the microphone devices to the combo box and set the current selection
 3899          SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(L"Default"));
 3900          selection = 0;
 3901          for( size_t i = 0; i < microphones.size(); i++ )
 3902          {
 3903              SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(microphones[i].second.c_str()) );
 3904              if( selection == 0 && wcscmp( microphones[i].first.c_str(), g_MicrophoneDeviceId ) == 0 )
 3905              {
 3906                  selection = i + 1;
 3907              }
 3908          }
 3909          SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), CB_SETCURSEL, static_cast<WPARAM>(selection), static_cast<LPARAM>(0) );
 3910  
 3911          // Set initial state of audio controls based on recording format (GIF has no audio)
 3912          bool isGifSelected = (g_RecordingFormat == RecordingFormat::GIF);
 3913          EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_SYSTEM_AUDIO), !isGifSelected);
 3914          EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO), !isGifSelected);
 3915          EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE_LABEL), !isGifSelected);
 3916          EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE), !isGifSelected);
 3917  
 3918          if( GetFileAttributes( g_DemoTypeFile ) == -1 )
 3919          {
 3920              memset( g_DemoTypeFile, 0, sizeof( g_DemoTypeFile ) );
 3921          }
 3922          else
 3923          {
 3924              SetDlgItemText( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_FILE, g_DemoTypeFile );
 3925          }
 3926          SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_SPEED_SLIDER ), TBM_SETRANGE, false, MAKELONG( MAX_TYPING_SPEED, MIN_TYPING_SPEED ) );
 3927          SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_SPEED_SLIDER ), TBM_SETPOS, true, g_DemoTypeSpeedSlider );
 3928          CheckDlgButton( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_USER_DRIVEN, g_DemoTypeUserDriven ? BST_CHECKED: BST_UNCHECKED );
 3929  
 3930          // Apply DPI scaling to the main dialog and to controls inside tab pages.
 3931          // Note: Scaling the main dialog only scales its direct children (including the
 3932          // tab page windows), but NOT the controls contained within the tab pages.
 3933          // So we scale each tab page's child controls separately.
 3934          currentDpi = GetDpiForWindowHelper( hDlg );
 3935          if( currentDpi != DPI_BASELINE )
 3936          {
 3937              ScaleDialogForDpi( hDlg, currentDpi, DPI_BASELINE );
 3938  
 3939              for( int i = 0; i < sizeof( g_OptionsTabs ) / sizeof( g_OptionsTabs[0] ); i++ )
 3940              {
 3941                  if( g_OptionsTabs[i].hPage )
 3942                  {
 3943                      ScaleChildControlsForDpi( g_OptionsTabs[i].hPage, currentDpi, DPI_BASELINE );
 3944                  }
 3945              }
 3946          }
 3947          // Always reposition tab pages to fit the tab control (whether scaled or not)
 3948          RepositionTabPages( hTabCtrl );
 3949  
 3950          // Initialize DPI-aware fonts after scaling so text sizing is correct.
 3951          InitializeFonts( hDlg, &hFontBold );
 3952          UpdateDrawTabHeaderFont();
 3953          UpdateVersionFont();
 3954  
 3955          // Always render the header labels using our static text subclass (even in light mode)
 3956          // so the larger title font is honored.
 3957          if( HWND hVersion = GetDlgItem( hDlg, IDC_VERSION ) )
 3958          {
 3959              SetWindowSubclass( hVersion, StaticTextSubclassProc, 55, 0 );
 3960          }
 3961          if( HWND hCopyright = GetDlgItem( hDlg, IDC_COPYRIGHT ) )
 3962          {
 3963              SetWindowSubclass( hCopyright, StaticTextSubclassProc, 56, 0 );
 3964          }
 3965  
 3966          // Apply dark mode to the dialog and all tab pages
 3967          ApplyDarkModeToDialog( hDlg );
 3968          for( int i = 0; i < sizeof( g_OptionsTabs ) / sizeof( g_OptionsTabs[0] ); i++ )
 3969          {
 3970              if( g_OptionsTabs[i].hPage )
 3971              {
 3972                  ApplyDarkModeToDialog( g_OptionsTabs[i].hPage );
 3973              }
 3974          }
 3975  
 3976          UnregisterAllHotkeys(GetParent( hDlg ));
 3977  
 3978          // Center dialog on screen, clamping to fit if it's too large for the work area
 3979          {
 3980              RECT rcDlg;
 3981              GetWindowRect(hDlg, &rcDlg);
 3982              int dlgWidth = rcDlg.right - rcDlg.left;
 3983              int dlgHeight = rcDlg.bottom - rcDlg.top;
 3984  
 3985              // Get the monitor where the cursor is
 3986              POINT pt;
 3987              GetCursorPos(&pt);
 3988              HMONITOR hMon = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
 3989              MONITORINFO mi = { sizeof(mi) };
 3990              GetMonitorInfo(hMon, &mi);
 3991  
 3992              // Calculate available work area size
 3993              const int workWidth = mi.rcWork.right - mi.rcWork.left;
 3994              const int workHeight = mi.rcWork.bottom - mi.rcWork.top;
 3995  
 3996              // Clamp dialog size to fit within work area (with a small margin)
 3997              constexpr int kMargin = 8;
 3998              if (dlgWidth > workWidth - kMargin * 2)
 3999              {
 4000                  dlgWidth = workWidth - kMargin * 2;
 4001              }
 4002              if (dlgHeight > workHeight - kMargin * 2)
 4003              {
 4004                  dlgHeight = workHeight - kMargin * 2;
 4005              }
 4006  
 4007              // Apply clamped size if it changed
 4008              if (dlgWidth != (rcDlg.right - rcDlg.left) || dlgHeight != (rcDlg.bottom - rcDlg.top))
 4009              {
 4010                  SetWindowPos(hDlg, nullptr, 0, 0, dlgWidth, dlgHeight, SWP_NOMOVE | SWP_NOZORDER);
 4011              }
 4012  
 4013              int x = mi.rcWork.left + (workWidth - dlgWidth) / 2;
 4014              int y = mi.rcWork.top + (workHeight - dlgHeight) / 2;
 4015              SetWindowPos(hDlg, nullptr, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
 4016          }
 4017  
 4018          // Capture a stable window size so per-monitor DPI changes won't resize/reflow the dialog.
 4019          GetWindowRect(hDlg, &stableWindowRect);
 4020          stableWindowRectValid = true;
 4021  
 4022          PostMessage( hDlg, WM_USER, 0, 0 );
 4023          // Reapply header fonts once the dialog has finished any late initialization.
 4024          PostMessage( hDlg, WM_APPLY_HEADER_FONTS, 0, 0 );
 4025  
 4026          // Set focus to the tab control instead of the first hotkey control
 4027          SetFocus( hTabCtrl );
 4028          return FALSE;
 4029      }
 4030  
 4031      case WM_APPLY_HEADER_FONTS:
 4032          InitializeFonts( hDlg, &hFontBold );
 4033          UpdateDrawTabHeaderFont();
 4034          UpdateVersionFont();
 4035          return TRUE;
 4036  
 4037      case WM_USER+100:
 4038          BringWindowToTop( hDlg );
 4039          SetFocus( hDlg );
 4040          SetForegroundWindow( hDlg );
 4041          return TRUE;
 4042  
 4043      case WM_DPICHANGED:
 4044      {
 4045          // Requirement: keep the Options dialog stable while it is open.
 4046          // Windows may already have resized the window by the time this arrives,
 4047          // so explicitly restore the previous size (but allow the suggested top-left).
 4048  
 4049          RECT* suggested = reinterpret_cast<RECT*>(lParam);
 4050          if (stableWindowRectValid && suggested)
 4051          {
 4052              const int stableW = stableWindowRect.right - stableWindowRect.left;
 4053              const int stableH = stableWindowRect.bottom - stableWindowRect.top;
 4054              SetWindowPos(hDlg, nullptr,
 4055                  suggested->left,
 4056                  suggested->top,
 4057                  stableW,
 4058                  stableH,
 4059                  SWP_NOZORDER | SWP_NOACTIVATE);
 4060          }
 4061          return TRUE;
 4062      }
 4063  
 4064      case WM_ERASEBKGND:
 4065          if (IsDarkModeEnabled())
 4066          {
 4067              HDC hdc = reinterpret_cast<HDC>(wParam);
 4068              RECT rc;
 4069              GetClientRect(hDlg, &rc);
 4070              FillRect(hdc, &rc, GetDarkModeBrush());
 4071              return TRUE;
 4072          }
 4073          break;
 4074  
 4075      case WM_CTLCOLORDLG:
 4076      case WM_CTLCOLORSTATIC:
 4077      case WM_CTLCOLORBTN:
 4078      case WM_CTLCOLOREDIT:
 4079      case WM_CTLCOLORLISTBOX:
 4080      {
 4081          HDC hdc = reinterpret_cast<HDC>(wParam);
 4082          HWND hCtrl = reinterpret_cast<HWND>(lParam);
 4083  
 4084          // Always force the Options header title to use the large version font.
 4085          // Note: We must also return a brush in light mode
 4086          // dialog proc may ignore our HDC changes.
 4087          if( message == WM_CTLCOLORSTATIC && hCtrl == GetDlgItem( hDlg, IDC_VERSION ) && hFontVersion )
 4088          {
 4089              SetBkMode( hdc, TRANSPARENT );
 4090              SelectObject( hdc, hFontVersion );
 4091  
 4092  #if _DEBUG
 4093              OutputDebug( L"WM_CTLCOLORSTATIC IDC_VERSION: dark=%d font=%p\n", IsDarkModeEnabled() ? 1 : 0, hFontVersion );
 4094  #endif
 4095  
 4096              if( !IsDarkModeEnabled() )
 4097              {
 4098                  // Light mode: explicitly return the dialog background brush.
 4099                  return reinterpret_cast<INT_PTR>(GetSysColorBrush( COLOR_BTNFACE ));
 4100              }
 4101          }
 4102  
 4103          // Handle dark mode colors
 4104          HBRUSH hBrush = HandleDarkModeCtlColor(hdc, hCtrl, message);
 4105          if (hBrush)
 4106          {
 4107              // Ensure the header version text uses the title font in dark mode.
 4108              if( message == WM_CTLCOLORSTATIC && hCtrl == GetDlgItem( hDlg, IDC_VERSION ) && hFontVersion )
 4109              {
 4110                  SelectObject( hdc, hFontVersion );
 4111              }
 4112  
 4113              // For bold title controls, also set the bold font
 4114              if (message == WM_CTLCOLORSTATIC &&
 4115                  (hCtrl == GetDlgItem(hDlg, IDC_TITLE) ||
 4116                   hCtrl == GetDlgItem(hDlg, IDC_DRAWING) ||
 4117                   hCtrl == GetDlgItem(hDlg, IDC_ZOOM) ||
 4118                   hCtrl == GetDlgItem(hDlg, IDC_BREAK) ||
 4119                   hCtrl == GetDlgItem(hDlg, IDC_TYPE)))
 4120              {
 4121                  SelectObject(hdc, hFontBold);
 4122              }
 4123              return reinterpret_cast<INT_PTR>(hBrush);
 4124          }
 4125  
 4126          // Light mode handling for bold title controls
 4127          if (message == WM_CTLCOLORSTATIC &&
 4128              (hCtrl == GetDlgItem(hDlg, IDC_TITLE) ||
 4129               hCtrl == GetDlgItem(hDlg, IDC_DRAWING) ||
 4130               hCtrl == GetDlgItem(hDlg, IDC_ZOOM) ||
 4131               hCtrl == GetDlgItem(hDlg, IDC_BREAK) ||
 4132               hCtrl == GetDlgItem(hDlg, IDC_TYPE)))
 4133          {
 4134              SetBkMode(hdc, TRANSPARENT);
 4135              SelectObject(hdc, hFontBold);
 4136              return reinterpret_cast<INT_PTR>(GetSysColorBrush(COLOR_BTNFACE));
 4137          }
 4138          break;
 4139      }
 4140  
 4141      case WM_SETTINGCHANGE:
 4142          // Handle theme change (dark/light mode toggle)
 4143          if (lParam && (wcscmp(reinterpret_cast<LPCWSTR>(lParam), L"ImmersiveColorSet") == 0))
 4144          {
 4145              RefreshDarkModeState();
 4146              ApplyDarkModeToDialog(hDlg);
 4147              for (int i = 0; i < sizeof(g_OptionsTabs) / sizeof(g_OptionsTabs[0]); i++)
 4148              {
 4149                  if (g_OptionsTabs[i].hPage)
 4150                  {
 4151                      ApplyDarkModeToDialog(g_OptionsTabs[i].hPage);
 4152                  }
 4153              }
 4154              InvalidateRect(hDlg, nullptr, TRUE);
 4155              for (int i = 0; i < sizeof(g_OptionsTabs) / sizeof(g_OptionsTabs[0]); i++)
 4156              {
 4157                  if (g_OptionsTabs[i].hPage)
 4158                  {
 4159                      InvalidateRect(g_OptionsTabs[i].hPage, nullptr, TRUE);
 4160                  }
 4161              }
 4162          }
 4163          break;
 4164  
 4165      case WM_DRAWITEM:
 4166      {
 4167          // Handle owner-draw for tab control in dark mode
 4168          DRAWITEMSTRUCT* pDIS = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
 4169          if (pDIS->CtlID == IDC_TAB && IsDarkModeEnabled())
 4170          {
 4171              // Fill tab background
 4172              HBRUSH hBrush = GetDarkModeBrush();
 4173              FillRect(pDIS->hDC, &pDIS->rcItem, hBrush);
 4174  
 4175              // Get tab text
 4176              TCITEM tci = {};
 4177              tci.mask = TCIF_TEXT;
 4178              TCHAR szText[128] = { 0 };
 4179              tci.pszText = szText;
 4180              tci.cchTextMax = _countof(szText);
 4181              TabCtrl_GetItem(hTabCtrl, pDIS->itemID, &tci);
 4182  
 4183              // Draw text
 4184              SetBkMode(pDIS->hDC, TRANSPARENT);
 4185              bool isSelected = (pDIS->itemState & ODS_SELECTED) != 0;
 4186              SetTextColor(pDIS->hDC, isSelected ? DarkMode::TextColor : DarkMode::DisabledTextColor);
 4187  
 4188              // Draw underline for selected tab
 4189              if (isSelected)
 4190              {
 4191                  RECT rcUnderline = pDIS->rcItem;
 4192                  rcUnderline.top = rcUnderline.bottom - 2;
 4193                  HBRUSH hAccent = CreateSolidBrush(DarkMode::AccentColor);
 4194                  FillRect(pDIS->hDC, &rcUnderline, hAccent);
 4195                  DeleteObject(hAccent);
 4196              }
 4197  
 4198              RECT rcText = pDIS->rcItem;
 4199              rcText.top += 4;
 4200              DrawText(pDIS->hDC, szText, -1, &rcText, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
 4201              return TRUE;
 4202          }
 4203          break;
 4204      }
 4205  
 4206      case WM_NOTIFY:
 4207          notify = reinterpret_cast<PNMLINK>(lParam);
 4208          if( notify->hdr.idFrom == IDC_LINK )
 4209          {
 4210              switch( notify->hdr.code )
 4211              {
 4212              case NM_CLICK:
 4213              case NM_RETURN:
 4214                  ShellExecute( hDlg, _T("open"), notify->item.szUrl, NULL, NULL, SW_SHOWNORMAL );
 4215                  break;
 4216              }
 4217          }
 4218          else switch( notify->hdr.code )
 4219          {
 4220          case TCN_SELCHANGE:
 4221              ShowWindow( g_OptionsTabs[curTabSel].hPage, SW_HIDE );
 4222              curTabSel = TabCtrl_GetCurSel(hTabCtrl);
 4223              ShowWindow( g_OptionsTabs[curTabSel].hPage, SW_SHOW );
 4224              break;
 4225          }
 4226          break;
 4227  
 4228      case WM_COMMAND:
 4229          switch ( LOWORD( wParam )) {
 4230          case IDOK:
 4231          {
 4232              if( !ConfigureAutostart( hDlg, IsDlgButtonChecked( hDlg, IDC_AUTOSTART) == BST_CHECKED )) {
 4233  
 4234                  break;
 4235              }
 4236              g_ShowTrayIcon = IsDlgButtonChecked( hDlg, IDC_SHOW_TRAY_ICON ) == BST_CHECKED;
 4237              g_AnimateZoom = IsDlgButtonChecked( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ANIMATE_ZOOM ) == BST_CHECKED;
 4238              g_SmoothImage = IsDlgButtonChecked( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_SMOOTH_IMAGE ) == BST_CHECKED;
 4239              g_DemoTypeUserDriven = IsDlgButtonChecked( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_USER_DRIVEN ) == BST_CHECKED;
 4240  
 4241              newToggleKey = static_cast<DWORD>(SendMessage( GetDlgItem( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_HOTKEY), HKM_GETHOTKEY, 0, 0 ));
 4242              newLiveZoomToggleKey = static_cast<DWORD>(SendMessage( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_LIVE_HOTKEY), HKM_GETHOTKEY, 0, 0 ));
 4243              newDrawToggleKey = static_cast<DWORD>(SendMessage( GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_DRAW_HOTKEY), HKM_GETHOTKEY, 0, 0 ));
 4244              newBreakToggleKey = static_cast<DWORD>(SendMessage( GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_BREAK_HOTKEY), HKM_GETHOTKEY, 0, 0 ));
 4245              newDemoTypeToggleKey = static_cast<DWORD>(SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_HOTKEY ), HKM_GETHOTKEY, 0, 0 ));
 4246              newRecordToggleKey = static_cast<DWORD>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_HOTKEY), HKM_GETHOTKEY, 0, 0));
 4247              newSnipToggleKey = static_cast<DWORD>(SendMessage( GetDlgItem( g_OptionsTabs[SNIP_PAGE].hPage, IDC_SNIP_HOTKEY), HKM_GETHOTKEY, 0, 0 ));
 4248  
 4249              newToggleMod = GetKeyMod( newToggleKey );
 4250              newLiveZoomToggleMod = GetKeyMod( newLiveZoomToggleKey );
 4251              newDrawToggleMod = GetKeyMod( newDrawToggleKey );
 4252              newBreakToggleMod = GetKeyMod( newBreakToggleKey );
 4253              newDemoTypeToggleMod = GetKeyMod( newDemoTypeToggleKey );
 4254              newRecordToggleMod = GetKeyMod(newRecordToggleKey);
 4255              newSnipToggleMod = GetKeyMod( newSnipToggleKey );
 4256  
 4257              g_SliderZoomLevel = static_cast<int>(SendMessage( GetDlgItem(g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ZOOM_SLIDER), TBM_GETPOS, 0, 0 ));
 4258              g_DemoTypeSpeedSlider = static_cast<int>(SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_SPEED_SLIDER ), TBM_GETPOS, 0, 0 ));
 4259  
 4260              g_ShowExpiredTime = IsDlgButtonChecked(  g_OptionsTabs[BREAK_PAGE].hPage, IDC_CHECK_SHOW_EXPIRED ) == BST_CHECKED;
 4261              g_CaptureSystemAudio = IsDlgButtonChecked(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_SYSTEM_AUDIO) == BST_CHECKED;
 4262              g_CaptureAudio = IsDlgButtonChecked(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO) == BST_CHECKED;
 4263              GetDlgItemText( g_OptionsTabs[BREAK_PAGE].hPage, IDC_TIMER, text, 3 );
 4264              text[2] = 0;
 4265              newTimeout = _tstoi( text );
 4266  
 4267              g_RecordingFormat = static_cast<RecordingFormat>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FORMAT), static_cast<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(0)));
 4268              g_RecordFrameRate = (g_RecordingFormat == RecordingFormat::GIF) ? RECORDING_FORMAT_GIF_DEFAULT_FRAMERATE : RECORDING_FORMAT_MP4_DEFAULT_FRAMERATE;
 4269              g_RecordScaling = static_cast<int>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), static_cast<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(0)) * 10 + 10);
 4270  
 4271              // Get the selected microphone
 4272              int index = static_cast<int>(SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(0) ));
 4273              _tcscpy( g_MicrophoneDeviceId, index == 0 ? L"" : microphones[static_cast<size_t>(index) - 1].first.c_str() );
 4274  
 4275              if( newToggleKey && !RegisterHotKey( GetParent( hDlg ), ZOOM_HOTKEY, newToggleMod, newToggleKey & 0xFF )) {
 4276  
 4277                  MessageBox( hDlg, L"The specified zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.",
 4278                      APPNAME, MB_ICONERROR );
 4279                  UnregisterAllHotkeys(GetParent( hDlg ));
 4280                  break;
 4281  
 4282              } else if(newLiveZoomToggleKey &&
 4283                  (!RegisterHotKey( GetParent( hDlg ), LIVE_HOTKEY, newLiveZoomToggleMod, newLiveZoomToggleKey & 0xFF ) ||
 4284                  !RegisterHotKey(GetParent(hDlg), LIVE_DRAW_HOTKEY, (newLiveZoomToggleMod ^ MOD_SHIFT), newLiveZoomToggleKey & 0xFF))) {
 4285  
 4286                  MessageBox( hDlg, L"The specified live-zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.",
 4287                      APPNAME, MB_ICONERROR );
 4288                  UnregisterAllHotkeys(GetParent( hDlg ));
 4289                  break;
 4290  
 4291              } else if( newDrawToggleKey && !RegisterHotKey( GetParent( hDlg ), DRAW_HOTKEY, newDrawToggleMod, newDrawToggleKey & 0xFF )) {
 4292  
 4293                  MessageBox( hDlg, L"The specified draw w/out zoom hotkey is already in use.\nSelect a different draw w/out zoom hotkey.",
 4294                      APPNAME, MB_ICONERROR );
 4295                  UnregisterAllHotkeys(GetParent( hDlg ));
 4296                  break;
 4297  
 4298              } else if( newBreakToggleKey && !RegisterHotKey( GetParent( hDlg ), BREAK_HOTKEY, newBreakToggleMod, newBreakToggleKey & 0xFF )) {
 4299  
 4300                  MessageBox( hDlg, L"The specified break timer hotkey is already in use.\nSelect a different break timer hotkey.",
 4301                      APPNAME, MB_ICONERROR );
 4302                  UnregisterAllHotkeys(GetParent( hDlg ));
 4303                  break;
 4304  
 4305              } else if( newDemoTypeToggleKey &&
 4306                  (!RegisterHotKey( GetParent( hDlg ), DEMOTYPE_HOTKEY, newDemoTypeToggleMod, newDemoTypeToggleKey & 0xFF ) ||
 4307                      !RegisterHotKey(GetParent(hDlg), DEMOTYPE_RESET_HOTKEY, (newDemoTypeToggleMod ^ MOD_SHIFT), newDemoTypeToggleKey & 0xFF))) {
 4308  
 4309                  MessageBox( hDlg, L"The specified live-type hotkey is already in use.\nSelect a different live-type hotkey.",
 4310                      APPNAME, MB_ICONERROR );
 4311                  UnregisterAllHotkeys( GetParent( hDlg ) );
 4312                  break;
 4313  
 4314              }
 4315              else if (newSnipToggleKey &&
 4316                  (!RegisterHotKey(GetParent(hDlg), SNIP_HOTKEY, newSnipToggleMod, newSnipToggleKey & 0xFF) ||
 4317                   !RegisterHotKey(GetParent(hDlg), SNIP_SAVE_HOTKEY, (newSnipToggleMod ^ MOD_SHIFT), newSnipToggleKey & 0xFF))) {
 4318  
 4319                  MessageBox(hDlg, L"The specified snip hotkey is already in use.\nSelect a different snip hotkey.",
 4320                      APPNAME, MB_ICONERROR);
 4321                  UnregisterAllHotkeys(GetParent(hDlg));
 4322                  break;
 4323  
 4324              }
 4325              else if( newRecordToggleKey &&
 4326                  (!RegisterHotKey(GetParent(hDlg), RECORD_HOTKEY,      newRecordToggleMod | MOD_NOREPEAT, newRecordToggleKey & 0xFF) ||
 4327                   !RegisterHotKey(GetParent(hDlg), RECORD_CROP_HOTKEY, (newRecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, newRecordToggleKey & 0xFF) ||
 4328                   !RegisterHotKey(GetParent(hDlg), RECORD_WINDOW_HOTKEY, (newRecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, newRecordToggleKey & 0xFF))) {
 4329  
 4330                  MessageBox(hDlg, L"The specified record hotkey is already in use.\nSelect a different record hotkey.",
 4331                      APPNAME, MB_ICONERROR);
 4332                  UnregisterAllHotkeys(GetParent(hDlg));
 4333                  break;
 4334  
 4335              } else {
 4336  
 4337                  g_BreakTimeout = newTimeout;
 4338                  g_ToggleKey = newToggleKey;
 4339                  g_LiveZoomToggleKey = newLiveZoomToggleKey;
 4340                  g_ToggleMod = newToggleMod;
 4341                  g_DrawToggleKey = newDrawToggleKey;
 4342                  g_DrawToggleMod = newDrawToggleMod;
 4343                  g_BreakToggleKey = newBreakToggleKey;
 4344                  g_BreakToggleMod = newBreakToggleMod;
 4345                  g_DemoTypeToggleKey = newDemoTypeToggleKey;
 4346                  g_DemoTypeToggleMod = newDemoTypeToggleMod;
 4347                  g_RecordToggleKey = newRecordToggleKey;
 4348                  g_RecordToggleMod = newRecordToggleMod;
 4349                  g_SnipToggleKey = newSnipToggleKey;
 4350                  g_SnipToggleMod = newSnipToggleMod;
 4351                  reg.WriteRegSettings( RegSettings );
 4352                  EnableDisableTrayIcon( GetParent( hDlg ), g_ShowTrayIcon );
 4353  
 4354                  hWndOptions = NULL;
 4355                  CleanupFonts();
 4356                  EndDialog( hDlg, 0 );
 4357                  return TRUE;
 4358              }
 4359              break;
 4360          }
 4361  
 4362          case IDCANCEL:
 4363              RegisterAllHotkeys(GetParent(hDlg));
 4364              hWndOptions = NULL;
 4365              CleanupFonts();
 4366              EndDialog( hDlg, 0 );
 4367              return TRUE;
 4368          }
 4369          break;
 4370  
 4371      case WM_CLOSE:
 4372          hWndOptions = NULL;
 4373          RegisterAllHotkeys(GetParent(hDlg));
 4374          CleanupFonts();
 4375          EndDialog( hDlg, 0 );
 4376          return TRUE;
 4377  
 4378      default:
 4379          break;
 4380      }
 4381      return FALSE;
 4382  }
 4383  
 4384  //----------------------------------------------------------------------------
 4385  //
 4386  // DeleteDrawUndoList
 4387  //
 4388  //----------------------------------------------------------------------------
 4389  void DeleteDrawUndoList( P_DRAW_UNDO *DrawUndoList )
 4390  {
 4391      P_DRAW_UNDO	nextUndo;
 4392  
 4393      nextUndo = *DrawUndoList;
 4394      while( nextUndo ) {
 4395  
 4396          *DrawUndoList = nextUndo->Next;
 4397          DeleteObject( nextUndo->hBitmap );
 4398          DeleteDC( nextUndo->hDc );
 4399          free( nextUndo );
 4400          nextUndo = *DrawUndoList;
 4401      }
 4402      *DrawUndoList = NULL;
 4403  }
 4404  
 4405  //----------------------------------------------------------------------------
 4406  //
 4407  // PopDrawUndo
 4408  //
 4409  //----------------------------------------------------------------------------
 4410  BOOLEAN PopDrawUndo( HDC hDc, P_DRAW_UNDO *DrawUndoList,
 4411                    int width, int height )
 4412  {
 4413      P_DRAW_UNDO	nextUndo;
 4414  
 4415      nextUndo = *DrawUndoList;
 4416      if( nextUndo ) {
 4417  
 4418          BitBlt( hDc, 0, 0, width, height,
 4419              nextUndo->hDc, 0, 0, SRCCOPY|CAPTUREBLT );
 4420          *DrawUndoList = nextUndo->Next;
 4421          DeleteObject( nextUndo->hBitmap );
 4422          DeleteDC( nextUndo->hDc );
 4423          free( nextUndo );
 4424          return TRUE;
 4425  
 4426      } else {
 4427  
 4428          Beep( 700, 200 );
 4429          return FALSE;
 4430      }
 4431  }
 4432  
 4433  
 4434  //----------------------------------------------------------------------------
 4435  //
 4436  // DeleteOldestUndo
 4437  //
 4438  //----------------------------------------------------------------------------
 4439  void DeleteOldestUndo( P_DRAW_UNDO *DrawUndoList )
 4440  {
 4441      P_DRAW_UNDO	nextUndo, freeUndo = NULL, prevUndo = NULL;
 4442  
 4443      nextUndo = *DrawUndoList;
 4444      freeUndo = nextUndo;
 4445      do {
 4446  
 4447          prevUndo = freeUndo;
 4448          freeUndo = nextUndo;
 4449          nextUndo = nextUndo->Next;
 4450  
 4451      } while( nextUndo );
 4452  
 4453      if( freeUndo ) {
 4454  
 4455          DeleteObject( freeUndo->hBitmap );
 4456          DeleteDC( freeUndo->hDc );
 4457          free( freeUndo );
 4458          if( prevUndo != *DrawUndoList ) prevUndo->Next = NULL;
 4459          else *DrawUndoList = NULL;
 4460      }
 4461  }
 4462  
 4463  //----------------------------------------------------------------------------
 4464  //
 4465  // GetOldestUndo
 4466  //
 4467  //----------------------------------------------------------------------------
 4468  P_DRAW_UNDO GetOldestUndo(P_DRAW_UNDO DrawUndoList)
 4469  {
 4470      P_DRAW_UNDO	nextUndo, oldestUndo = NULL;
 4471  
 4472      nextUndo = DrawUndoList;
 4473      oldestUndo = nextUndo;
 4474      do {
 4475  
 4476          oldestUndo = nextUndo;
 4477          nextUndo = nextUndo->Next;
 4478  
 4479      } while( nextUndo );
 4480      return oldestUndo;
 4481  }
 4482  
 4483  
 4484  //----------------------------------------------------------------------------
 4485  //
 4486  // PushDrawUndo
 4487  //
 4488  //----------------------------------------------------------------------------
 4489  void PushDrawUndo( HDC hDc, P_DRAW_UNDO *DrawUndoList, int width, int height )
 4490  {
 4491      P_DRAW_UNDO	nextUndo, newUndo;
 4492      int			i = 0;
 4493      HBITMAP		hUndoBitmap;
 4494  
 4495      OutputDebug(L"PushDrawUndo\n");
 4496  
 4497      // Don't store more than 8 undo's (XP gets really upset when we
 4498      // exhaust heap with them)
 4499      nextUndo = *DrawUndoList;
 4500      do {
 4501  
 4502          i++;
 4503          if( i == MAX_UNDO_HISTORY ) {
 4504  
 4505              DeleteOldestUndo( DrawUndoList );
 4506              break;
 4507          }
 4508          if( nextUndo ) nextUndo = nextUndo->Next;
 4509  
 4510      } while( nextUndo );
 4511  
 4512      hUndoBitmap = CreateCompatibleBitmap( hDc, width, height );
 4513      if( !hUndoBitmap && *DrawUndoList ) {
 4514  
 4515          // delete the oldest and try again
 4516          DeleteOldestUndo( DrawUndoList );
 4517          hUndoBitmap = CreateCompatibleBitmap( hDc, width, height );
 4518      }
 4519      if( hUndoBitmap ) {
 4520  
 4521          newUndo = static_cast<P_DRAW_UNDO>(malloc( sizeof( DRAW_UNDO )));
 4522          if (newUndo != NULL)
 4523          {
 4524              newUndo->hDc = CreateCompatibleDC(hDc);
 4525              newUndo->hBitmap = hUndoBitmap;
 4526              SelectObject(newUndo->hDc, newUndo->hBitmap);
 4527              BitBlt(newUndo->hDc, 0, 0, width, height, hDc, 0, 0, SRCCOPY | CAPTUREBLT);
 4528              newUndo->Next = *DrawUndoList;
 4529              *DrawUndoList = newUndo;
 4530          }
 4531      }
 4532  }
 4533  
 4534  //----------------------------------------------------------------------------
 4535  //
 4536  // DeleteTypedText
 4537  //
 4538  //----------------------------------------------------------------------------
 4539  void DeleteTypedText( P_TYPED_KEY *TypedKeyList )
 4540  {
 4541      P_TYPED_KEY	nextKey;
 4542  
 4543      while( *TypedKeyList ) {
 4544  
 4545          nextKey = (*TypedKeyList)->Next;
 4546          free( *TypedKeyList );
 4547          *TypedKeyList = nextKey;
 4548      }
 4549  }
 4550  
 4551  //----------------------------------------------------------------------------
 4552  //
 4553  // BlankScreenArea
 4554  //
 4555  //----------------------------------------------------------------------------
 4556  void BlankScreenArea( HDC hDc, PRECT Rc, int BlankMode )
 4557  {
 4558      if( BlankMode == 'K' ) {
 4559  
 4560          HBRUSH hBrush = CreateSolidBrush( RGB( 0, 0, 0 ));
 4561          FillRect( hDc, Rc, hBrush );
 4562          DeleteObject( static_cast<HGDIOBJ>(hBrush) );
 4563  
 4564      } else {
 4565  
 4566          FillRect( hDc, Rc, GetSysColorBrush( COLOR_WINDOW ));
 4567      }
 4568  }
 4569  
 4570  //----------------------------------------------------------------------------
 4571  //
 4572  // ClearTypingCursor
 4573  //
 4574  //----------------------------------------------------------------------------
 4575  void ClearTypingCursor( HDC hdcScreenCompat, HDC hdcScreenCursorCompat, RECT rc,
 4576                              int BlankMode )
 4577  {
 4578      if( false ) { // BlankMode ) {
 4579  
 4580          BlankScreenArea( hdcScreenCompat, &rc, BlankMode );
 4581  
 4582      } else {
 4583  
 4584          BitBlt(hdcScreenCompat, rc.left, rc.top, rc.right - rc.left,
 4585              rc.bottom - rc.top, hdcScreenCursorCompat,0, 0, SRCCOPY|CAPTUREBLT );
 4586      }
 4587  }
 4588  
 4589  //----------------------------------------------------------------------------
 4590  //
 4591  // DrawTypingCursor
 4592  //
 4593  //----------------------------------------------------------------------------
 4594  void DrawTypingCursor( HWND hWnd, POINT *textPt, HDC hdcScreenCompat,
 4595  	HDC hdcScreenCursorCompat, RECT *rc, bool centerUnderSystemCursor = false )
 4596  {
 4597  	// Draw the typing cursor
 4598  	rc->left = textPt->x;
 4599  	rc->top = textPt->y;
 4600  	TCHAR vKey = '|';
 4601  	DrawText( hdcScreenCompat, static_cast<PTCHAR>(&vKey), 1, rc, DT_CALCRECT );
 4602  
 4603  	// LiveDraw uses a layered window which means mouse messages pass through
 4604  	//   to lower windows unless the system cursor is above a painted area.
 4605  	// Centering the typing cursor directly under the system cursor allows
 4606  	//   us to capture the mouse wheel input required to change font size.
 4607  	if( centerUnderSystemCursor )
 4608  	{
 4609  		const LONG halfWidth  = static_cast<LONG>( (rc->right - rc->left) / 2 );
 4610  		const LONG halfHeight = static_cast<LONG>( (rc->bottom - rc->top) / 2 );
 4611  
 4612  		rc->left   -= halfWidth;
 4613  		rc->right  -= halfWidth;
 4614  		rc->top    -= halfHeight;
 4615  		rc->bottom -= halfHeight;
 4616  
 4617  		textPt->x   = rc->left;
 4618  		textPt->y   = rc->top;
 4619  	}
 4620  
 4621  	BitBlt(hdcScreenCursorCompat, 0, 0, rc->right -rc->left, rc->bottom - rc->top,
 4622  		hdcScreenCompat, rc->left, rc->top, SRCCOPY|CAPTUREBLT );
 4623  
 4624  	DrawText( hdcScreenCompat, static_cast<PTCHAR>(&vKey), 1, rc, DT_LEFT );
 4625  	InvalidateRect( hWnd, NULL, TRUE );
 4626  }
 4627  
 4628  //----------------------------------------------------------------------------
 4629  //
 4630  // BoundMouse
 4631  //
 4632  //----------------------------------------------------------------------------
 4633  RECT BoundMouse( float zoomLevel, MONITORINFO *monInfo, int width, int height,
 4634                      POINT *cursorPos )
 4635  {
 4636      RECT		rc;
 4637      int			x, y;
 4638  
 4639      GetZoomedTopLeftCoordinates( zoomLevel, cursorPos, &x, width, &y, height );
 4640      rc.left = monInfo->rcMonitor.left + x;
 4641      rc.right = rc.left + static_cast<int>(width/zoomLevel);
 4642      rc.top = monInfo->rcMonitor.top + y;
 4643      rc.bottom = rc.top + static_cast<int>(height/zoomLevel);
 4644  
 4645      OutputDebug( L"x: %d y: %d width: %d height: %d zoomLevel: %g\n",
 4646          cursorPos->x, cursorPos->y, width, height, zoomLevel);
 4647      OutputDebug( L"left: %d top: %d right: %d bottom: %d\n",
 4648              rc.left, rc.top, rc.right, rc.bottom);
 4649      OutputDebug( L"mon.left: %d mon.top: %d mon.right: %d mon.bottom: %d\n",
 4650          monInfo->rcMonitor.left, monInfo->rcMonitor.top, monInfo->rcMonitor.right, monInfo->rcMonitor.bottom);
 4651  
 4652      ClipCursor( &rc );
 4653      return rc;
 4654  }
 4655  
 4656  //----------------------------------------------------------------------------
 4657  //
 4658  // DrawArrow
 4659  //
 4660  //----------------------------------------------------------------------------
 4661  void DrawArrow( HDC hdc, int x1, int y1, int x2, int y2, double length, double width,
 4662          bool UseGdiplus )
 4663  {
 4664      // get normalized dx/dy
 4665      double dx = static_cast<double>(x2) - x1;
 4666      double dy = static_cast<double>(y2) - y1;
 4667      double bodyLen = sqrt( dx*dx + dy*dy );
 4668      if ( bodyLen )  {
 4669          dx /= bodyLen;
 4670          dy /= bodyLen;
 4671      } else {
 4672          dx = 1;
 4673          dy = 0;
 4674      }
 4675  
 4676      // get midpoint of base
 4677      int xMid = x2 - static_cast<int>(length*dx+0.5);
 4678      int yMid = y2 - static_cast<int>(length*dy+0.5);
 4679  
 4680      // get left wing
 4681      int xLeft = xMid - static_cast<int>(dy*width+0.5);
 4682      int yLeft = yMid + static_cast<int>(dx*width+0.5);
 4683  
 4684      // get right wing
 4685      int xRight = xMid + static_cast<int>(dy*width+0.5);
 4686      int yRight = yMid - static_cast<int>(dx*width+0.5);
 4687  
 4688      // Bring in midpoint to make a nicer arrow
 4689      xMid = x2 - static_cast<int>(length/2*dx+0.5);
 4690      yMid = y2 - static_cast<int>(length/2*dy+0.5);
 4691      if (UseGdiplus) {
 4692  
 4693          Gdiplus::Graphics	dstGraphics(hdc);
 4694  
 4695          if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
 4696          {
 4697              dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
 4698          }
 4699          Gdiplus::Color	color = ColorFromColorRef(g_PenColor);
 4700          Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
 4701          pen.SetLineCap(Gdiplus::LineCapRound, Gdiplus::LineCapRound, Gdiplus::DashCapRound);
 4702  #if 0
 4703          Gdiplus::PointF	pts[] = {
 4704              {(Gdiplus::REAL)x1, (Gdiplus::REAL)y1},
 4705              {(Gdiplus::REAL)xMid, (Gdiplus::REAL)yMid},
 4706              {(Gdiplus::REAL)xLeft, (Gdiplus::REAL)yLeft},
 4707              {(Gdiplus::REAL)x2, (Gdiplus::REAL)y2},
 4708              {(Gdiplus::REAL)xRight, (Gdiplus::REAL)yRight},
 4709              {(Gdiplus::REAL)xMid, (Gdiplus::REAL)yMid}
 4710          };
 4711          dstGraphics.DrawPolygon(&pen, pts, _countof(pts));
 4712  #else
 4713          Gdiplus::GraphicsPath path;
 4714          path.StartFigure();
 4715          path.AddLine(static_cast<INT>(x1), static_cast<INT>(y1), static_cast<INT>(x2), static_cast<INT>(y2));
 4716          path.AddLine(static_cast<INT>(x2), static_cast<INT>(y2), static_cast<INT>(xMid), static_cast<INT>(yMid));
 4717          path.AddLine(static_cast<INT>(xMid), static_cast<INT>(yMid), static_cast<INT>(xLeft), static_cast<INT>(yLeft));
 4718          path.AddLine(static_cast<INT>(xLeft), static_cast<INT>(yLeft), static_cast<INT>(x2), static_cast<INT>(y2));
 4719          path.AddLine(static_cast<INT>(x2), static_cast<INT>(y2), static_cast<INT>(xRight), static_cast<INT>(yRight));
 4720          path.AddLine(static_cast<INT>(xRight), static_cast<INT>(yRight), static_cast<INT>(xMid), static_cast<INT>(yMid));
 4721          pen.SetLineJoin(Gdiplus::LineJoinRound);
 4722          dstGraphics.DrawPath(&pen, &path);
 4723  #endif
 4724      }
 4725      else {
 4726          POINT	pts[] = {
 4727              x1, y1,
 4728              xMid, yMid,
 4729              xLeft, yLeft,
 4730              x2, y2,
 4731              xRight, yRight,
 4732              xMid, yMid
 4733          };
 4734  
 4735          // draw arrow head filled with current color
 4736          HBRUSH hBrush = CreateSolidBrush(g_PenColor);
 4737          HBRUSH hOldBrush = SelectBrush(hdc, hBrush);
 4738          Polygon(hdc, pts, sizeof(pts) / sizeof(pts[0]));
 4739  
 4740          DeleteObject(hBrush);
 4741          SelectObject(hdc, hOldBrush);
 4742      }
 4743  }
 4744  
 4745  
 4746  
 4747  //----------------------------------------------------------------------------
 4748  //
 4749  // DrawShape
 4750  //
 4751  //----------------------------------------------------------------------------
 4752  VOID DrawShape( DWORD Shape, HDC hDc, RECT *Rect, bool UseGdiPlus = false )
 4753  {
 4754      bool	isBlur = false;
 4755  
 4756      Gdiplus::Graphics	dstGraphics(hDc);
 4757  	if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
 4758  	{
 4759  		dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
 4760  	}
 4761      Gdiplus::Color	color = ColorFromColorRef(g_PenColor);
 4762      Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
 4763      pen.SetLineCap(Gdiplus::LineCapRound, Gdiplus::LineCapRound, Gdiplus::DashCapRound);
 4764  
 4765      // Check for highlighting or blur
 4766      Gdiplus::Brush *pBrush = NULL;
 4767      if (PEN_COLOR_HIGHLIGHT(g_PenColor)) {
 4768          // Use half the alpha for higher contrast
 4769          DWORD newColor = g_PenColor & 0xFFFFFF | ((g_AlphaBlend / 2) << 24);
 4770          pBrush = new Gdiplus::SolidBrush(ColorFromColorRef(newColor));
 4771          if(UseGdiPlus && Shape != DRAW_LINE && Shape != DRAW_ARROW)
 4772              InflateRect(Rect, g_PenWidth/2, g_PenWidth/2);
 4773      }
 4774      else if ((g_PenColor & 0xFFFFFF) == COLOR_BLUR) {
 4775          if (UseGdiPlus && Shape != DRAW_LINE && Shape != DRAW_ARROW)
 4776              InflateRect(Rect, g_PenWidth / 2, g_PenWidth / 2);
 4777          isBlur = true;
 4778      }
 4779      OutputDebug(L"Draw shape: highlight: %d pbrush: %d\n", PEN_COLOR_HIGHLIGHT(g_PenColor), pBrush != NULL);
 4780  
 4781      switch (Shape) {
 4782      case DRAW_RECTANGLE:
 4783          if (UseGdiPlus)
 4784              if(pBrush)
 4785                  DrawHighlightedShape(DRAW_RECTANGLE, hDc, pBrush, NULL,
 4786                      static_cast<int>(Rect->left - 1), static_cast<int>(Rect->top - 1),
 4787                      static_cast<int>(Rect->right), static_cast<int>(Rect->bottom));
 4788              else if (isBlur)
 4789                  DrawBlurredShape( DRAW_RECTANGLE, &pen, hDc, &dstGraphics,
 4790                      static_cast<int>(Rect->left - 1), static_cast<int>(Rect->top - 1),
 4791                      static_cast<int>(Rect->right), static_cast<int>(Rect->bottom) );
 4792              else
 4793                  dstGraphics.DrawRectangle(&pen,
 4794                      Gdiplus::Rect::Rect(Rect->left - 1, Rect->top - 1,
 4795                          Rect->right - Rect->left, Rect->bottom - Rect->top));
 4796          else
 4797              Rectangle(hDc, Rect->left, Rect->top,
 4798                  Rect->right, Rect->bottom);
 4799          break;
 4800      case DRAW_ELLIPSE:
 4801          if (UseGdiPlus)
 4802              if (pBrush)
 4803                  DrawHighlightedShape(DRAW_ELLIPSE, hDc, pBrush, NULL,
 4804                      static_cast<int>(Rect->left - 1), static_cast<int>(Rect->top - 1),
 4805                      static_cast<int>(Rect->right), static_cast<int>(Rect->bottom));
 4806              else if (isBlur)
 4807                  DrawBlurredShape( DRAW_ELLIPSE, &pen, hDc, &dstGraphics,
 4808                      static_cast<int>(Rect->left - 1), static_cast<int>(Rect->top - 1),
 4809                      static_cast<int>(Rect->right), static_cast<int>(Rect->bottom));
 4810              else
 4811                  dstGraphics.DrawEllipse(&pen,
 4812                      Gdiplus::Rect::Rect(Rect->left - 1, Rect->top - 1,
 4813                          Rect->right - Rect->left, Rect->bottom - Rect->top));
 4814          else
 4815              Ellipse(hDc, Rect->left, Rect->top,
 4816                  Rect->right, Rect->bottom);
 4817          break;
 4818      case DRAW_LINE:
 4819          if (UseGdiPlus)
 4820              if (pBrush)
 4821                  DrawHighlightedShape(DRAW_LINE, hDc, NULL, &pen,
 4822                      static_cast<int>(Rect->left), static_cast<int>(Rect->top),
 4823                      static_cast<int>(Rect->right), static_cast<int>(Rect->bottom));
 4824              else if (isBlur)
 4825                  DrawBlurredShape(DRAW_LINE, &pen, hDc, &dstGraphics,
 4826                      static_cast<int>(Rect->left), static_cast<int>(Rect->top),
 4827                      static_cast<int>(Rect->right), static_cast<int>(Rect->bottom));
 4828              else
 4829                  dstGraphics.DrawLine(&pen,
 4830                      static_cast<INT>(Rect->left - 1), static_cast<INT>(Rect->top - 1),
 4831                      static_cast<INT>(Rect->right), static_cast<INT>(Rect->bottom));
 4832          else {
 4833              MoveToEx(hDc, Rect->left, Rect->top, NULL);
 4834              LineTo(hDc, Rect->right + 1, Rect->bottom + 1);
 4835          }
 4836          break;
 4837      case DRAW_ARROW:
 4838          DrawArrow(hDc, Rect->right + 1, Rect->bottom + 1,
 4839              Rect->left, Rect->top,
 4840              static_cast<double>(g_PenWidth) * 2.5, static_cast<double>(g_PenWidth) * 1.5, UseGdiPlus);
 4841          break;
 4842      }
 4843      if( pBrush ) delete pBrush;
 4844  }
 4845  
 4846  //----------------------------------------------------------------------------
 4847  //
 4848  // SendPenMessage
 4849  //
 4850  // Inserts the pen message marker.
 4851  //
 4852  //----------------------------------------------------------------------------
 4853  VOID SendPenMessage(HWND hWnd, UINT Message, LPARAM lParam)
 4854  {
 4855      WPARAM		wParam = 0;
 4856      //
 4857      // Get key states
 4858      //
 4859      if(GetKeyState(VK_LCONTROL) < 0 ) {
 4860  
 4861          wParam |= MK_CONTROL;
 4862      }
 4863      if( GetKeyState( VK_LSHIFT) < 0 || GetKeyState( VK_RSHIFT) < 0 ) {
 4864  
 4865          wParam |= MK_SHIFT;
 4866      }
 4867      SetMessageExtraInfo(static_cast<LPARAM>(MI_WP_SIGNATURE));
 4868      SendMessage(hWnd, Message, wParam, lParam);
 4869  }
 4870  
 4871  
 4872  //----------------------------------------------------------------------------
 4873  //
 4874  // ScalePenPosition
 4875  //
 4876  // Maps pen input to mouse input coordinates based on zoom level. Returns
 4877  // 0 if pen is active but we didn't send this message to ourselves (pen
 4878  // signature will be missing).
 4879  //
 4880  //----------------------------------------------------------------------------
 4881  LPARAM ScalePenPosition( float zoomLevel, MONITORINFO *monInfo, RECT boundRc,
 4882                      UINT message, LPARAM lParam )
 4883  {
 4884      RECT	rc;
 4885      WORD	x, y;
 4886      LPARAM	extraInfo;
 4887  
 4888      extraInfo = GetMessageExtraInfo();
 4889      if( g_PenDown ) {
 4890  
 4891          // ignore messages we didn't tag as pen
 4892          if (extraInfo == MI_WP_SIGNATURE) {
 4893  
 4894              OutputDebug( L"Tablet Pen message\n");
 4895  
 4896              // tablet input: don't bound the cursor
 4897              ClipCursor(NULL);
 4898  
 4899              x = LOWORD(lParam);
 4900              y = HIWORD(lParam);
 4901  
 4902              x = static_cast<WORD>((x - static_cast<WORD>(monInfo->rcMonitor.left))/ zoomLevel) + static_cast<WORD>(boundRc.left - monInfo->rcMonitor.left);
 4903              y = static_cast<WORD>((y - static_cast<WORD>(monInfo->rcMonitor.top)) / zoomLevel) + static_cast<WORD>(boundRc.top - monInfo->rcMonitor.top);
 4904  
 4905              lParam = MAKELPARAM(x, y);
 4906          }
 4907          else {
 4908  
 4909              OutputDebug(L"Ignore pen message we didn't send\n");
 4910              lParam = 0;
 4911          }
 4912  
 4913      } else {
 4914  
 4915          if( !GetClipCursor( &rc )) {
 4916  
 4917              ClipCursor( &boundRc );
 4918          }
 4919          OutputDebug( L"Mouse message\n");
 4920      }
 4921      return lParam;
 4922  }
 4923  
 4924  
 4925  //----------------------------------------------------------------------------
 4926  //
 4927  // DrawHighlightedCursor
 4928  //
 4929  //----------------------------------------------------------------------------
 4930  BOOLEAN DrawHighlightedCursor( float ZoomLevel, int Width, int Height )
 4931  {
 4932      DWORD zoomWidth = static_cast<DWORD> (static_cast<float>(Width)/ZoomLevel);
 4933      DWORD zoomHeight = static_cast<DWORD> (static_cast<float>(Height)/ZoomLevel);
 4934      if( g_PenWidth < 5 && zoomWidth > g_PenWidth * 100 && zoomHeight > g_PenWidth * 100 ) {
 4935  
 4936          return TRUE;
 4937  
 4938      } else {
 4939  
 4940          return FALSE;
 4941      }
 4942  }
 4943  
 4944  //----------------------------------------------------------------------------
 4945  //
 4946  // InvalidateCursorMoveArea
 4947  //
 4948  //----------------------------------------------------------------------------
 4949  void InvalidateCursorMoveArea( HWND hWnd, float zoomLevel, int width, int height,
 4950                                POINT currentPt, POINT prevPt, POINT cursorPos )
 4951  {
 4952      int		x, y;
 4953      RECT	rc;
 4954      int		invWidth = g_PenWidth + CURSOR_SAVE_MARGIN;
 4955  
 4956      if( DrawHighlightedCursor( zoomLevel, width, height ) ) {
 4957  
 4958          invWidth = g_PenWidth * 3 + 1;
 4959      }
 4960      GetZoomedTopLeftCoordinates( zoomLevel, &cursorPos, &x, width, &y, height );
 4961      rc.left = static_cast<int>(max( 0, (int) ((min( prevPt.x, currentPt.x)-invWidth - x) * zoomLevel)));
 4962      rc.right = static_cast<int>((max( prevPt.x, currentPt.x)+invWidth - x) * zoomLevel);
 4963      rc.top = static_cast<int>(max( 0, (int) ((min( prevPt.y, currentPt.y)-invWidth - y) * zoomLevel)));
 4964      rc.bottom = static_cast<int>((max( prevPt.y, currentPt.y)+invWidth -y) * zoomLevel);
 4965      InvalidateRect( hWnd, &rc, FALSE );
 4966  
 4967      OutputDebug( L"INVALIDATE: (%d, %d) - (%d, %d)\n", rc.left, rc.top, rc.right, rc.bottom);
 4968  }
 4969  
 4970  
 4971  //----------------------------------------------------------------------------
 4972  //
 4973  // SavCursorArea
 4974  //
 4975  //----------------------------------------------------------------------------
 4976  void SaveCursorArea( HDC hDcTarget, HDC hDcSource, POINT pt )
 4977  {
 4978      OutputDebug( L"SaveCursorArea\n");
 4979      int penWidth = g_PenWidth + CURSOR_SAVE_MARGIN;
 4980      BitBlt( hDcTarget, 0, 0, penWidth +CURSOR_ARM_LENGTH*2, penWidth +CURSOR_ARM_LENGTH*2,
 4981          hDcSource, static_cast<INT> (pt.x- penWidth /2)-CURSOR_ARM_LENGTH,
 4982          static_cast<INT>(pt.y- penWidth /2)-CURSOR_ARM_LENGTH, SRCCOPY|CAPTUREBLT );
 4983  }
 4984  
 4985  //----------------------------------------------------------------------------
 4986  //
 4987  // RestoreCursorArea
 4988  //
 4989  //----------------------------------------------------------------------------
 4990  void RestoreCursorArea( HDC hDcTarget, HDC hDcSource, POINT pt )
 4991  {
 4992      OutputDebug( L"RestoreCursorArea\n");
 4993      int penWidth = g_PenWidth + CURSOR_SAVE_MARGIN;
 4994      BitBlt( hDcTarget, static_cast<INT>(pt.x- penWidth /2)-CURSOR_ARM_LENGTH,
 4995          static_cast<INT>(pt.y- penWidth /2)-CURSOR_ARM_LENGTH, penWidth +CURSOR_ARM_LENGTH*2,
 4996          penWidth + CURSOR_ARM_LENGTH*2, hDcSource, 0, 0, SRCCOPY|CAPTUREBLT );
 4997  }
 4998  
 4999  
 5000  //----------------------------------------------------------------------------
 5001  //
 5002  // DrawCursor
 5003  //
 5004  //----------------------------------------------------------------------------
 5005  void DrawCursor( HDC hDcTarget, POINT pt, float ZoomLevel, int Width, int Height )
 5006  {
 5007      RECT	rc;
 5008  
 5009      if( g_DrawPointer ) {
 5010  
 5011          Gdiplus::Graphics	dstGraphics(hDcTarget);
 5012          if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
 5013          {
 5014              dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
 5015          }
 5016          Gdiplus::Color	color = ColorFromColorRef(g_PenColor);
 5017          Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
 5018  
 5019          rc.left = pt.x - CURSOR_ARM_LENGTH;
 5020          rc.right = pt.x + CURSOR_ARM_LENGTH;
 5021          rc.top = pt.y - CURSOR_ARM_LENGTH;
 5022          rc.bottom = pt.y + CURSOR_ARM_LENGTH;
 5023  
 5024          Gdiplus::GraphicsPath path;
 5025          path.StartFigure();
 5026          path.AddLine(static_cast<INT>(rc.left) - 1, static_cast<INT>(rc.top) - 1, static_cast<INT>(rc.right), static_cast<INT>(rc.bottom));
 5027          path.AddLine(static_cast<INT>(rc.left) - 2, static_cast<INT>(rc.top) - 1, rc.left + (rc.right - rc.left) / 2, rc.top - 1);
 5028          path.AddLine(static_cast<INT>(rc.left) - 1, static_cast<INT>(rc.top) - 2, rc.left - 1, rc.top + (rc.bottom - rc.top) / 2);
 5029          path.AddLine(static_cast<INT>(rc.left) - 1, static_cast<INT>(rc.top) - 2, rc.left - 1, rc.top + (rc.bottom - rc.top) / 2);
 5030          path.AddLine(static_cast<INT>(rc.left + (rc.right - rc.left) / 2), rc.top - 1, rc.left - 1, rc.top + (rc.bottom - rc.top) / 2);
 5031          pen.SetLineJoin(Gdiplus::LineJoinRound);
 5032          dstGraphics.DrawPath(&pen, &path);
 5033          OutputDebug(L"DrawPointer: %d %d %d %d\n", rc.left, rc.top, rc.right, rc.bottom);
 5034  
 5035      } else if( DrawHighlightedCursor( ZoomLevel, Width, Height )) {
 5036  
 5037          OutputDebug(L"DrawHighlightedCursor: %d %d %d %d\n", pt.x, pt.y, g_PenWidth, g_PenWidth);
 5038          Gdiplus::Graphics	dstGraphics(hDcTarget);
 5039          if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
 5040          {
 5041              dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
 5042          }
 5043          Gdiplus::Color	color = ColorFromColorRef(g_PenColor);
 5044          Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
 5045          Gdiplus::GraphicsPath path;
 5046          path.StartFigure();
 5047          pen.SetLineJoin(Gdiplus::LineJoinRound);
 5048          path.AddLine(static_cast<INT>(pt.x - CURSOR_ARM_LENGTH), pt.y, pt.x + CURSOR_ARM_LENGTH, pt.y);
 5049          path.CloseFigure();
 5050          path.StartFigure();
 5051          pen.SetLineJoin(Gdiplus::LineJoinRound);
 5052          path.AddLine(static_cast<INT>(pt.x), pt.y - CURSOR_ARM_LENGTH, pt.x, pt.y + CURSOR_ARM_LENGTH);
 5053          path.CloseFigure();
 5054          dstGraphics.DrawPath(&pen, &path);
 5055  
 5056      } else {
 5057  
 5058          Gdiplus::Graphics	dstGraphics(hDcTarget);
 5059          {
 5060              if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
 5061              {
 5062                  dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
 5063              }
 5064              Gdiplus::Color	color = ColorFromColorRef(g_PenColor);
 5065  
 5066              Gdiplus::SolidBrush solidBrush(color);
 5067  
 5068              dstGraphics.FillEllipse(&solidBrush, static_cast<INT>(pt.x-g_PenWidth/2), static_cast<INT>(pt.y-g_PenWidth/2),
 5069                          static_cast<INT>(g_PenWidth), static_cast<INT>(g_PenWidth));
 5070          }
 5071      }
 5072  }
 5073  
 5074  //----------------------------------------------------------------------------
 5075  //
 5076  // ResizePen
 5077  //
 5078  //----------------------------------------------------------------------------
 5079  void ResizePen( HWND hWnd, HDC hdcScreenCompat, HDC hdcScreenCursorCompat, POINT prevPt,
 5080                  BOOLEAN g_Tracing, BOOLEAN *g_Drawing, float g_LiveZoomLevel,
 5081                  BOOLEAN isUser, int newWidth )
 5082  {
 5083      if( !g_Tracing ) {
 5084  
 5085          RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
 5086      }
 5087  
 5088      OutputDebug( L"RESIZE_PEN-PRE: penWidth: %d ", g_PenWidth );
 5089      int prevWidth = g_PenWidth;
 5090      if( g_ZoomOnLiveZoom )
 5091      {
 5092          if( isUser )
 5093          {
 5094              // Amplify user delta proportional to LiveZoomLevel
 5095              newWidth = g_PenWidth + static_cast<int> ((newWidth - static_cast<int>(g_PenWidth))*g_LiveZoomLevel);
 5096          }
 5097  
 5098          g_PenWidth = min( max( newWidth, MIN_PEN_WIDTH ),
 5099              min( static_cast<int>(MAX_PEN_WIDTH * g_LiveZoomLevel), MAX_LIVE_PEN_WIDTH ) );
 5100          g_RootPenWidth = static_cast<int>(g_PenWidth / g_LiveZoomLevel);
 5101      }
 5102      else
 5103      {
 5104          g_PenWidth = min( max( newWidth, MIN_PEN_WIDTH ), MAX_PEN_WIDTH );
 5105          g_RootPenWidth = g_PenWidth;
 5106      }
 5107  
 5108      if(prevWidth == static_cast<int>(g_PenWidth) ) {
 5109          // No change
 5110          return;
 5111      }
 5112  
 5113      OutputDebug( L"newWidth: %d\nRESIZE_PEN-POST: penWidth: %d\n", newWidth, g_PenWidth );
 5114      reg.WriteRegSettings( RegSettings );
 5115      SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
 5116      *g_Drawing = FALSE;
 5117      EnableDisableStickyKeys( TRUE );
 5118      SendMessage( hWnd, WM_LBUTTONDOWN, -1, MAKELPARAM(prevPt.x, prevPt.y) );
 5119  }
 5120  
 5121  //----------------------------------------------------------------------------
 5122  //
 5123  // IsPenInverted
 5124  //
 5125  //----------------------------------------------------------------------------
 5126  bool IsPenInverted( WPARAM wParam )
 5127  {
 5128      POINTER_INPUT_TYPE pointerType;
 5129      POINTER_PEN_INFO penInfo;
 5130      return
 5131          pGetPointerType( GET_POINTERID_WPARAM( wParam ), &pointerType ) && ( pointerType == PT_PEN ) &&
 5132          pGetPointerPenInfo( GET_POINTERID_WPARAM( wParam ), &penInfo ) && ( penInfo.penFlags & PEN_FLAG_INVERTED );
 5133  }
 5134  
 5135  
 5136  //----------------------------------------------------------------------------
 5137  //
 5138  // CaptureScreenshotAsync
 5139  //
 5140  // Captures the specified screen using the capture APIs
 5141  //
 5142  //----------------------------------------------------------------------------
 5143  std::future<winrt::com_ptr<ID3D11Texture2D>> CaptureScreenshotAsync(winrt::IDirect3DDevice const& device, winrt::GraphicsCaptureItem const& item, winrt::DirectXPixelFormat const& pixelFormat)
 5144  {
 5145      auto d3dDevice = GetDXGIInterfaceFromObject<ID3D11Device>(device);
 5146      winrt::com_ptr<ID3D11DeviceContext> d3dContext;
 5147      d3dDevice->GetImmediateContext(d3dContext.put());
 5148  
 5149      // Creating our frame pool with CreateFreeThreaded means that we
 5150      // will be called back from the frame pool's internal worker thread
 5151      // instead of the thread we are currently on. It also disables the
 5152      // DispatcherQueue requirement.
 5153      auto framePool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(
 5154          device,
 5155          pixelFormat,
 5156          1,
 5157          item.Size());
 5158      auto session = framePool.CreateCaptureSession(item);
 5159  
 5160      wil::shared_event captureEvent(wil::EventOptions::ManualReset);
 5161      winrt::Direct3D11CaptureFrame frame{ nullptr };
 5162      framePool.FrameArrived([&frame, captureEvent](auto& framePool, auto&)
 5163          {
 5164              frame = framePool.TryGetNextFrame();
 5165  
 5166              // Complete the operation
 5167              captureEvent.SetEvent();
 5168          });
 5169  
 5170      session.IsCursorCaptureEnabled( false );
 5171      session.StartCapture();
 5172      co_await winrt::resume_on_signal(captureEvent.get());
 5173  
 5174      // End the capture
 5175      session.Close();
 5176      framePool.Close();
 5177  
 5178      auto texture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
 5179      auto result = util::CopyD3DTexture(d3dDevice, texture, true);
 5180  
 5181      co_return result;
 5182  }
 5183  
 5184  //----------------------------------------------------------------------------
 5185  //
 5186  // CaptureScreenshot
 5187  //
 5188  // Captures the specified screen using the capture APIs
 5189  //
 5190  //----------------------------------------------------------------------------
 5191  winrt::com_ptr<ID3D11Texture2D>CaptureScreenshot(winrt::DirectXPixelFormat const& pixelFormat)
 5192  {
 5193      auto d3dDevice = util::CreateD3D11Device();
 5194      auto dxgiDevice = d3dDevice.as<IDXGIDevice>();
 5195      auto device = CreateDirect3DDevice(dxgiDevice.get());
 5196  
 5197      // Get the active MONITOR capture device
 5198      HMONITOR hMon = NULL;
 5199      POINT cursorPos = { 0, 0 };
 5200      if (pMonitorFromPoint) {
 5201  
 5202          GetCursorPos(&cursorPos);
 5203          hMon = pMonitorFromPoint(cursorPos, MONITOR_DEFAULTTONEAREST);
 5204      }
 5205  
 5206      auto item = util::CreateCaptureItemForMonitor(hMon);
 5207  
 5208      auto capture = CaptureScreenshotAsync(device, item, pixelFormat);
 5209      capture.wait();
 5210  
 5211      return capture.get();
 5212  }
 5213  
 5214  
 5215  //----------------------------------------------------------------------------
 5216  //
 5217  // CopyD3DTexture
 5218  //
 5219  //----------------------------------------------------------------------------
 5220  inline auto CopyD3DTexture(winrt::com_ptr<ID3D11Device> const& device,
 5221              winrt::com_ptr<ID3D11Texture2D> const& texture, bool asStagingTexture)
 5222  {
 5223      winrt::com_ptr<ID3D11DeviceContext> context;
 5224      device->GetImmediateContext(context.put());
 5225  
 5226      D3D11_TEXTURE2D_DESC desc = {};
 5227      texture->GetDesc(&desc);
 5228      // Clear flags that we don't need
 5229      desc.Usage = asStagingTexture ? D3D11_USAGE_STAGING : D3D11_USAGE_DEFAULT;
 5230      desc.BindFlags = asStagingTexture ? 0 : D3D11_BIND_SHADER_RESOURCE;
 5231      desc.CPUAccessFlags = asStagingTexture ? D3D11_CPU_ACCESS_READ : 0;
 5232      desc.MiscFlags = 0;
 5233  
 5234      // Create and fill the texture copy
 5235      winrt::com_ptr<ID3D11Texture2D> textureCopy;
 5236      winrt::check_hresult(device->CreateTexture2D(&desc, nullptr, textureCopy.put()));
 5237      context->CopyResource(textureCopy.get(), texture.get());
 5238  
 5239      return textureCopy;
 5240  }
 5241  
 5242  
 5243  //----------------------------------------------------------------------------
 5244  //
 5245  // PrepareStagingTexture
 5246  //
 5247  //----------------------------------------------------------------------------
 5248  inline auto PrepareStagingTexture(winrt::com_ptr<ID3D11Device> const& device,
 5249              winrt::com_ptr<ID3D11Texture2D> const& texture)
 5250  {
 5251      // If our texture is already set up for staging, then use it.
 5252      D3D11_TEXTURE2D_DESC desc = {};
 5253      texture->GetDesc(&desc);
 5254      if (desc.Usage == D3D11_USAGE_STAGING && desc.CPUAccessFlags & D3D11_CPU_ACCESS_READ)
 5255      {
 5256          return texture;
 5257      }
 5258  
 5259      return CopyD3DTexture(device, texture, true);
 5260  }
 5261  
 5262  //----------------------------------------------------------------------------
 5263  //
 5264  // GetBytesPerPixel
 5265  //
 5266  //----------------------------------------------------------------------------
 5267  inline size_t
 5268  GetBytesPerPixel(DXGI_FORMAT pixelFormat)
 5269  {
 5270      switch (pixelFormat)
 5271      {
 5272      case DXGI_FORMAT_R32G32B32A32_TYPELESS:
 5273      case DXGI_FORMAT_R32G32B32A32_FLOAT:
 5274      case DXGI_FORMAT_R32G32B32A32_UINT:
 5275      case DXGI_FORMAT_R32G32B32A32_SINT:
 5276          return 16;
 5277      case DXGI_FORMAT_R32G32B32_TYPELESS:
 5278      case DXGI_FORMAT_R32G32B32_FLOAT:
 5279      case DXGI_FORMAT_R32G32B32_UINT:
 5280      case DXGI_FORMAT_R32G32B32_SINT:
 5281          return 12;
 5282      case DXGI_FORMAT_R16G16B16A16_TYPELESS:
 5283      case DXGI_FORMAT_R16G16B16A16_FLOAT:
 5284      case DXGI_FORMAT_R16G16B16A16_UNORM:
 5285      case DXGI_FORMAT_R16G16B16A16_UINT:
 5286      case DXGI_FORMAT_R16G16B16A16_SNORM:
 5287      case DXGI_FORMAT_R16G16B16A16_SINT:
 5288      case DXGI_FORMAT_R32G32_TYPELESS:
 5289      case DXGI_FORMAT_R32G32_FLOAT:
 5290      case DXGI_FORMAT_R32G32_UINT:
 5291      case DXGI_FORMAT_R32G32_SINT:
 5292      case DXGI_FORMAT_R32G8X24_TYPELESS:
 5293          return 8;
 5294      case DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
 5295      case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS:
 5296      case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT:
 5297      case DXGI_FORMAT_R10G10B10A2_TYPELESS:
 5298      case DXGI_FORMAT_R10G10B10A2_UNORM:
 5299      case DXGI_FORMAT_R10G10B10A2_UINT:
 5300      case DXGI_FORMAT_R11G11B10_FLOAT:
 5301      case DXGI_FORMAT_R8G8B8A8_TYPELESS:
 5302      case DXGI_FORMAT_R8G8B8A8_UNORM:
 5303      case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
 5304      case DXGI_FORMAT_R8G8B8A8_UINT:
 5305      case DXGI_FORMAT_R8G8B8A8_SNORM:
 5306      case DXGI_FORMAT_R8G8B8A8_SINT:
 5307      case DXGI_FORMAT_R16G16_TYPELESS:
 5308      case DXGI_FORMAT_R16G16_FLOAT:
 5309      case DXGI_FORMAT_UNKNOWN:
 5310      case DXGI_FORMAT_R16G16_UINT:
 5311      case DXGI_FORMAT_R16G16_SNORM:
 5312      case DXGI_FORMAT_R16G16_SINT:
 5313      case DXGI_FORMAT_R32_TYPELESS:
 5314      case DXGI_FORMAT_D32_FLOAT:
 5315      case DXGI_FORMAT_R32_FLOAT:
 5316      case DXGI_FORMAT_R32_UINT:
 5317      case DXGI_FORMAT_R32_SINT:
 5318      case DXGI_FORMAT_R24G8_TYPELESS:
 5319      case DXGI_FORMAT_D24_UNORM_S8_UINT:
 5320      case DXGI_FORMAT_R24_UNORM_X8_TYPELESS:
 5321      case DXGI_FORMAT_X24_TYPELESS_G8_UINT:
 5322      case DXGI_FORMAT_R8G8_B8G8_UNORM:
 5323      case DXGI_FORMAT_G8R8_G8B8_UNORM:
 5324      case DXGI_FORMAT_B8G8R8A8_UNORM:
 5325      case DXGI_FORMAT_B8G8R8X8_UNORM:
 5326      case DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM:
 5327      case DXGI_FORMAT_B8G8R8A8_TYPELESS:
 5328      case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
 5329      case DXGI_FORMAT_B8G8R8X8_TYPELESS:
 5330      case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
 5331          return 4;
 5332      case DXGI_FORMAT_R8G8_TYPELESS:
 5333      case DXGI_FORMAT_R8G8_UNORM:
 5334      case DXGI_FORMAT_R8G8_UINT:
 5335      case DXGI_FORMAT_R8G8_SNORM:
 5336      case DXGI_FORMAT_R8G8_SINT:
 5337      case DXGI_FORMAT_R16_TYPELESS:
 5338      case DXGI_FORMAT_R16_FLOAT:
 5339      case DXGI_FORMAT_D16_UNORM:
 5340      case DXGI_FORMAT_R16_UNORM:
 5341      case DXGI_FORMAT_R16_UINT:
 5342      case DXGI_FORMAT_R16_SNORM:
 5343      case DXGI_FORMAT_R16_SINT:
 5344      case DXGI_FORMAT_B5G6R5_UNORM:
 5345      case DXGI_FORMAT_B5G5R5A1_UNORM:
 5346      case DXGI_FORMAT_B4G4R4A4_UNORM:
 5347          return 2;
 5348      case DXGI_FORMAT_R8_TYPELESS:
 5349      case DXGI_FORMAT_R8_UNORM:
 5350      case DXGI_FORMAT_R8_UINT:
 5351      case DXGI_FORMAT_R8_SNORM:
 5352      case DXGI_FORMAT_R8_SINT:
 5353      case DXGI_FORMAT_A8_UNORM:
 5354          return 1;
 5355      default:
 5356          throw winrt::hresult_invalid_argument(L"Invalid pixel format!");
 5357      }
 5358  }
 5359  
 5360  //----------------------------------------------------------------------------
 5361  //
 5362  // CopyBytesFromTexture
 5363  //
 5364  //----------------------------------------------------------------------------
 5365  inline auto CopyBytesFromTexture(winrt::com_ptr<ID3D11Texture2D> const& texture, uint32_t subresource = 0)
 5366  {
 5367      winrt::com_ptr<ID3D11Device> device;
 5368      texture->GetDevice(device.put());
 5369      winrt::com_ptr<ID3D11DeviceContext> context;
 5370      device->GetImmediateContext(context.put());
 5371  
 5372      auto stagingTexture = PrepareStagingTexture(device, texture);
 5373  
 5374      D3D11_TEXTURE2D_DESC desc = {};
 5375      stagingTexture->GetDesc(&desc);
 5376      auto bytesPerPixel = GetBytesPerPixel(desc.Format);
 5377  
 5378      // Copy the bits
 5379      D3D11_MAPPED_SUBRESOURCE mapped = {};
 5380      winrt::check_hresult(context->Map(stagingTexture.get(), subresource, D3D11_MAP_READ, 0, &mapped));
 5381  
 5382      auto bytesStride = static_cast<size_t>(desc.Width) * bytesPerPixel;
 5383      std::vector<byte> bytes(bytesStride * static_cast<size_t>(desc.Height), 0);
 5384      auto source = static_cast<byte*>(mapped.pData);
 5385      auto dest = bytes.data();
 5386      for (auto i = 0; i < static_cast<int>(desc.Height); i++)
 5387      {
 5388          memcpy(dest, source, bytesStride);
 5389  
 5390          source += mapped.RowPitch;
 5391          dest += bytesStride;
 5392      }
 5393      context->Unmap(stagingTexture.get(), 0);
 5394  
 5395      return bytes;
 5396  }
 5397  
 5398  
 5399  //----------------------------------------------------------------------------
 5400  //
 5401  // StopRecording
 5402  //
 5403  //----------------------------------------------------------------------------
 5404  void StopRecording()
 5405  {
 5406      OutputDebugStringW(L"[Recording] StopRecording called\n");
 5407      if( g_RecordToggle == TRUE ) {
 5408  
 5409          OutputDebugStringW(L"[Recording] g_RecordToggle was TRUE, stopping...\n");
 5410          g_SelectRectangle.Stop();
 5411  
 5412          if ( g_RecordingSession != nullptr ) {
 5413  
 5414              OutputDebugStringW(L"[Recording] Closing VideoRecordingSession\n");
 5415              g_RecordingSession->Close();
 5416              // NOTE: Do NOT null the session here - let the coroutine finish first
 5417          }
 5418  
 5419          if ( g_GifRecordingSession != nullptr ) {
 5420  
 5421              OutputDebugStringW(L"[Recording] Closing GifRecordingSession\n");
 5422              g_GifRecordingSession->Close();
 5423              // NOTE: Do NOT null the session here - let the coroutine finish first
 5424          }
 5425  
 5426          g_RecordToggle = FALSE;
 5427  #if WINDOWS_CURSOR_RECORDING_WORKAROUND
 5428  
 5429          if( g_hWndLiveZoom != NULL && g_LiveZoomLevelOne ) {
 5430  
 5431              if( IsWindowVisible( g_hWndLiveZoom ) ) {
 5432  
 5433                  ShowWindow( g_hWndLiveZoom, SW_HIDE );
 5434                  DestroyWindow( g_hWndLiveZoom );
 5435                  g_LiveZoomLevelOne = false;
 5436              }
 5437          }
 5438  #endif
 5439      }
 5440  }
 5441  
 5442  
 5443  //----------------------------------------------------------------------------
 5444  //
 5445  // GetUniqueFilename
 5446  //
 5447  // Returns a unique filename by checking for existing files and adding (1), (2), etc.
 5448  // suffixes as needed. Uses the folder from lastSavePath if available
 5449  //
 5450  //----------------------------------------------------------------------------
 5451  auto GetUniqueFilename(const std::wstring& lastSavePath, const wchar_t* defaultFilename, REFKNOWNFOLDERID defaultFolderId)
 5452  {
 5453      // Get the folder where the file will be saved
 5454      std::filesystem::path saveFolder;
 5455      if (!lastSavePath.empty())
 5456      {
 5457          // Use folder from last save location
 5458          saveFolder = std::filesystem::path(lastSavePath).parent_path();
 5459      }
 5460  
 5461      if (saveFolder.empty())
 5462      {
 5463          // Default to specified known folder
 5464          wil::unique_cotaskmem_string folderPath;
 5465          if (SUCCEEDED(SHGetKnownFolderPath(defaultFolderId, KF_FLAG_DEFAULT, nullptr, folderPath.put())))
 5466          {
 5467              saveFolder = folderPath.get();
 5468          }
 5469      }
 5470  
 5471      // Build base name and extension
 5472      std::filesystem::path defaultPath = defaultFilename;
 5473      auto base = defaultPath.stem().wstring();
 5474      auto ext = defaultPath.extension().wstring();
 5475  
 5476      // Check for existing files and find unique name
 5477      std::wstring candidateName = base + ext;
 5478      std::filesystem::path checkPath = saveFolder / candidateName;
 5479  
 5480      int index = 1;
 5481      std::error_code ec;
 5482      while (std::filesystem::exists(checkPath, ec))
 5483      {
 5484          candidateName = base + L" (" + std::to_wstring(index) + L")" + ext;
 5485          checkPath = saveFolder / candidateName;
 5486          index++;
 5487      }
 5488  
 5489      return candidateName;
 5490  }
 5491  
 5492  //----------------------------------------------------------------------------
 5493  //
 5494  // GetUniqueRecordingFilename
 5495  //
 5496  // Gets a unique file name for recording saves, using the " (N)" suffix
 5497  // approach so that the user can hit OK without worrying about overwriting
 5498  // if they are making multiple recordings in one session or don't want to
 5499  // always see an overwrite dialog or stop to clean up files.
 5500  //
 5501  //----------------------------------------------------------------------------
 5502  auto GetUniqueRecordingFilename()
 5503  {
 5504      const wchar_t* defaultFile = (g_RecordingFormat == RecordingFormat::GIF)
 5505          ? DEFAULT_GIF_RECORDING_FILE
 5506          : DEFAULT_RECORDING_FILE;
 5507  
 5508      return GetUniqueFilename(g_RecordingSaveLocation, defaultFile, FOLDERID_Videos);
 5509  }
 5510  
 5511  auto GetUniqueScreenshotFilename()
 5512  {
 5513      return GetUniqueFilename(g_ScreenshotSaveLocation, DEFAULT_SCREENSHOT_FILE, FOLDERID_Pictures);
 5514  }
 5515  
 5516  //----------------------------------------------------------------------------
 5517  //
 5518  // StartRecordingAsync
 5519  //
 5520  // Starts the screen recording.
 5521  //
 5522  //----------------------------------------------------------------------------
 5523  winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndRecord ) try
 5524  {
 5525      // Capture the UI thread context so we can resume on it for the save dialog
 5526      winrt::apartment_context uiThread;
 5527  
 5528      auto tempFolderPath = std::filesystem::temp_directory_path().wstring();
 5529      auto tempFolder = co_await winrt::StorageFolder::GetFolderFromPathAsync( tempFolderPath );
 5530      auto appFolder = co_await tempFolder.CreateFolderAsync( L"ZoomIt", winrt::CreationCollisionOption::OpenIfExists );
 5531  
 5532      // Choose temp file extension based on format
 5533      const wchar_t* tempFileName = (g_RecordingFormat == RecordingFormat::GIF) ? L"zoomit.gif" : L"zoomit.mp4";
 5534      auto file = co_await appFolder.CreateFileAsync( tempFileName, winrt::CreationCollisionOption::ReplaceExisting );
 5535  
 5536      // Get the device
 5537      auto d3dDevice = util::CreateD3D11Device();
 5538      auto dxgiDevice = d3dDevice.as<IDXGIDevice>();
 5539      g_RecordDevice = CreateDirect3DDevice( dxgiDevice.get() );
 5540  
 5541      // Get the active MONITOR capture device
 5542      HMONITOR hMon = NULL;
 5543      POINT cursorPos = { 0, 0 };
 5544      if( pMonitorFromPoint )	{
 5545  
 5546          GetCursorPos( &cursorPos );
 5547          hMon = pMonitorFromPoint( cursorPos, MONITOR_DEFAULTTONEAREST );
 5548      }
 5549  
 5550      winrt::Windows::Graphics::Capture::GraphicsCaptureItem item{ nullptr };
 5551      if( hWndRecord )
 5552          item = util::CreateCaptureItemForWindow( hWndRecord );
 5553      else
 5554          item = util::CreateCaptureItemForMonitor( hMon );
 5555  
 5556      auto stream = co_await file.OpenAsync( winrt::FileAccessMode::ReadWrite );
 5557  
 5558      // Create the appropriate recording session based on format
 5559      OutputDebugStringW((L"Starting recording session. Framerate:  " + std::to_wstring(g_RecordFrameRate) + L" scaling: " + std::to_wstring(g_RecordScaling) + L" Format: " + (g_RecordingFormat == RecordingFormat::GIF ? L"GIF" : L"MP4") + L"\n").c_str());
 5560      bool recordingStarted = false;
 5561      HRESULT captureStatus = S_OK;
 5562  
 5563      if (g_RecordingFormat == RecordingFormat::GIF)
 5564      {
 5565          g_GifRecordingSession = GifRecordingSession::Create(
 5566                                          g_RecordDevice,
 5567                                          item,
 5568                                          *rcCrop,
 5569                                          g_RecordFrameRate,
 5570                                          stream );
 5571  
 5572          recordingStarted = (g_GifRecordingSession != nullptr);
 5573  
 5574          if( g_hWndLiveZoom != NULL )
 5575              g_GifRecordingSession->EnableCursorCapture( false );
 5576  
 5577          if (recordingStarted)
 5578          {
 5579              try
 5580              {
 5581                  co_await g_GifRecordingSession->StartAsync();
 5582              }
 5583              catch (const winrt::hresult_error& error)
 5584              {
 5585                  captureStatus = error.code();
 5586                  OutputDebugStringW((L"Recording session failed: " + error.message() + L"\n").c_str());
 5587              }
 5588          }
 5589  
 5590          // If no frames were captured, behave as if the hotkey was never pressed.
 5591          if (recordingStarted && g_GifRecordingSession && !g_GifRecordingSession->HasCapturedFrames())
 5592          {
 5593              if (stream)
 5594              {
 5595                  stream.Close();
 5596                  stream = nullptr;
 5597              }
 5598              try { co_await file.DeleteAsync(); } catch (...) {}
 5599              g_RecordingSession = nullptr;
 5600              g_GifRecordingSession = nullptr;
 5601              co_return;
 5602          }
 5603      }
 5604      else
 5605      {
 5606          g_RecordingSession = VideoRecordingSession::Create(
 5607                                          g_RecordDevice,
 5608                                          item,
 5609                                          *rcCrop,
 5610                                          g_RecordFrameRate,
 5611                                          g_CaptureAudio,
 5612                                          g_CaptureSystemAudio,
 5613                                          stream );
 5614  
 5615          recordingStarted = (g_RecordingSession != nullptr);
 5616  
 5617          if( g_hWndLiveZoom != NULL )
 5618              g_RecordingSession->EnableCursorCapture( false );
 5619  
 5620          if (recordingStarted)
 5621          {
 5622              try
 5623              {
 5624                  co_await g_RecordingSession->StartAsync();
 5625              }
 5626              catch (const winrt::hresult_error& error)
 5627              {
 5628                  captureStatus = error.code();
 5629                  OutputDebugStringW((L"Recording session failed: " + error.message() + L"\n").c_str());
 5630              }
 5631          }
 5632  
 5633          // If no frames were captured, behave as if the hotkey was never pressed.
 5634          if (recordingStarted && g_RecordingSession && !g_RecordingSession->HasCapturedVideoFrames())
 5635          {
 5636              if (stream)
 5637              {
 5638                  stream.Close();
 5639                  stream = nullptr;
 5640              }
 5641              try { co_await file.DeleteAsync(); } catch (...) {}
 5642              g_RecordingSession = nullptr;
 5643              g_GifRecordingSession = nullptr;
 5644              co_return;
 5645          }
 5646      }
 5647  
 5648      // If we never created a session, bail and clean up the temp file silently
 5649      if( !recordingStarted ) {
 5650  
 5651          if (stream) {
 5652              stream.Close();
 5653              stream = nullptr;
 5654          }
 5655          try { co_await file.DeleteAsync(); } catch (...) {}
 5656          co_return;
 5657      }
 5658  
 5659      // Recording completed (closed via hotkey or item close). Proceed to save/trim workflow.
 5660      OutputDebugStringW(L"[Recording] StartAsync completed, entering save workflow\n");
 5661  
 5662      // Resume on the UI thread for the save dialog
 5663      co_await uiThread;
 5664      OutputDebugStringW(L"[Recording] Resumed on UI thread\n");
 5665  
 5666      {
 5667  
 5668          g_bSaveInProgress = true;
 5669  
 5670          SendMessage( g_hWndMain, WM_USER_SAVE_CURSOR, 0, 0 );
 5671  
 5672          winrt::StorageFile destFile = nullptr;
 5673          HRESULT hr = S_OK;
 5674          try {
 5675              // Show trim dialog option and save dialog
 5676              std::wstring trimmedFilePath;
 5677              auto suggestedName = GetUniqueRecordingFilename();
 5678              auto finalPath = VideoRecordingSession::ShowSaveDialogWithTrim(
 5679                  hWnd,
 5680                  suggestedName,
 5681                  std::wstring{ file.Path() },
 5682                  trimmedFilePath
 5683              );
 5684  
 5685              if (!finalPath.empty())
 5686              {
 5687                  auto path = std::filesystem::path(finalPath);
 5688                  winrt::StorageFolder folder{ co_await winrt::StorageFolder::GetFolderFromPathAsync(path.parent_path().c_str()) };
 5689                  destFile = co_await folder.CreateFileAsync(path.filename().c_str(), winrt::CreationCollisionOption::ReplaceExisting);
 5690  
 5691                  // If user trimmed, use the trimmed file
 5692                  winrt::StorageFile sourceFile = file;
 5693                  if (!trimmedFilePath.empty())
 5694                  {
 5695                      sourceFile = co_await winrt::StorageFile::GetFileFromPathAsync(trimmedFilePath);
 5696                  }
 5697  
 5698                  // Move the chosen source into the user-selected destination
 5699                  co_await sourceFile.MoveAndReplaceAsync(destFile);
 5700  
 5701                  // If we moved a trimmed copy, clean up the original temp capture file
 5702                  if (sourceFile != file)
 5703                  {
 5704                      try { co_await file.DeleteAsync(); } catch (...) {}
 5705                  }
 5706  
 5707                  // Use finalPath directly - destFile.Path() may be stale after MoveAndReplaceAsync
 5708                  g_RecordingSaveLocation = finalPath;
 5709                  // Update the registry buffer and save to persist across app restarts
 5710                  wcsncpy_s(g_RecordingSaveLocationBuffer, g_RecordingSaveLocation.c_str(), _TRUNCATE);
 5711                  reg.WriteRegSettings(RegSettings);
 5712                  SaveToClipboard(g_RecordingSaveLocation.c_str(), hWnd);
 5713              }
 5714              else
 5715              {
 5716                  // User cancelled
 5717                  hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
 5718              }
 5719  
 5720              //auto saveDialog = wil::CoCreateInstance<IFileSaveDialog>( CLSID_FileSaveDialog );
 5721              //FILEOPENDIALOGOPTIONS options;
 5722              //if( SUCCEEDED( saveDialog->GetOptions( &options ) ) )
 5723              //    saveDialog->SetOptions( options | FOS_FORCEFILESYSTEM );
 5724              //wil::com_ptr<IShellItem> videosItem;
 5725              //if( SUCCEEDED ( SHGetKnownFolderItem( FOLDERID_Videos, KF_FLAG_DEFAULT, nullptr, IID_IShellItem, (void**) videosItem.put() ) ) )
 5726              //    saveDialog->SetDefaultFolder( videosItem.get() );
 5727  
 5728              //// Set file type based on the recording format
 5729              //if (g_RecordingFormat == RecordingFormat::GIF)
 5730              //{
 5731              //    saveDialog->SetDefaultExtension( L".gif" );
 5732              //    COMDLG_FILTERSPEC fileTypes[] = {
 5733              //        { L"GIF Animation", L"*.gif" }
 5734              //    };
 5735              //    saveDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
 5736              //}
 5737              //else
 5738              //{
 5739              //    saveDialog->SetDefaultExtension( L".mp4" );
 5740              //    COMDLG_FILTERSPEC fileTypes[] = {
 5741              //        { L"MP4 Video", L"*.mp4" }
 5742              //    };
 5743              //    saveDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
 5744              //}
 5745  
 5746              //// Peek the folder Windows has chosen to display
 5747              //static std::filesystem::path lastSaveFolder;
 5748              //wil::unique_cotaskmem_string chosenFolderPath;
 5749              //wil::com_ptr<IShellItem> currentSelectedFolder;
 5750              //bool bFolderChanged = false;
 5751              //if (SUCCEEDED(saveDialog->GetFolder(currentSelectedFolder.put())))
 5752              //{
 5753              //    if (SUCCEEDED(currentSelectedFolder->GetDisplayName(SIGDN_FILESYSPATH, chosenFolderPath.put())))
 5754              //    {
 5755              //        if (lastSaveFolder != chosenFolderPath.get())
 5756              //        {
 5757              //            lastSaveFolder = chosenFolderPath.get() ? chosenFolderPath.get() : std::filesystem::path{};
 5758              //            bFolderChanged = true;
 5759              //        }
 5760              //    }
 5761              //}
 5762  
 5763              //if( (g_RecordingFormat == RecordingFormat::GIF && g_RecordingSaveLocationGIF.size() == 0) || (g_RecordingFormat == RecordingFormat::MP4 && g_RecordingSaveLocation.size() == 0) || (bFolderChanged)) {
 5764  
 5765              //    wil::com_ptr<IShellItem> shellItem;
 5766              //    wil::unique_cotaskmem_string folderPath;
 5767              //    if (SUCCEEDED(saveDialog->GetFolder(shellItem.put())) && SUCCEEDED(shellItem->GetDisplayName(SIGDN_FILESYSPATH, folderPath.put()))) {
 5768              //        if (g_RecordingFormat == RecordingFormat::GIF) {
 5769              //            g_RecordingSaveLocationGIF = folderPath.get();
 5770              //            std::filesystem::path currentPath{ g_RecordingSaveLocationGIF };
 5771              //            g_RecordingSaveLocationGIF = currentPath / DEFAULT_GIF_RECORDING_FILE;
 5772              //        }
 5773              //        else {
 5774              //            g_RecordingSaveLocation = folderPath.get();
 5775              //            if (g_RecordingFormat == RecordingFormat::MP4) {
 5776              //                std::filesystem::path currentPath{ g_RecordingSaveLocation };
 5777              //                g_RecordingSaveLocation = currentPath / DEFAULT_RECORDING_FILE;
 5778              //            }
 5779              //        }
 5780              //    }
 5781              //}
 5782  
 5783              //// Always use appropriate default filename based on current format
 5784              //auto suggestedName = GetUniqueRecordingFilename();
 5785              //saveDialog->SetFileName( suggestedName.c_str() );
 5786  
 5787              //THROW_IF_FAILED( saveDialog->Show( hWnd ) );
 5788              //wil::com_ptr<IShellItem> shellItem;
 5789              //THROW_IF_FAILED(saveDialog->GetResult(shellItem.put()));
 5790              //wil::unique_cotaskmem_string filePath;
 5791              //THROW_IF_FAILED(shellItem->GetDisplayName(SIGDN_FILESYSPATH, filePath.put()));
 5792              //auto path = std::filesystem::path( filePath.get() );
 5793  
 5794              //winrt::StorageFolder folder{ co_await winrt::StorageFolder::GetFolderFromPathAsync( path.parent_path().c_str() ) };
 5795              //destFile = co_await folder.CreateFileAsync( path.filename().c_str(), winrt::CreationCollisionOption::ReplaceExisting );
 5796          }
 5797          catch( const wil::ResultException& error ) {
 5798              OutputDebugStringW((L"[Recording] wil exception: hr=0x" + std::to_wstring(error.GetErrorCode()) + L"\n").c_str());
 5799              hr = error.GetErrorCode();
 5800          }
 5801          catch( const std::exception& ex ) {
 5802              OutputDebugStringA("[Recording] std::exception: ");
 5803              OutputDebugStringA(ex.what());
 5804              OutputDebugStringA("\n");
 5805              hr = E_FAIL;
 5806          }
 5807          catch( ... ) {
 5808              OutputDebugStringW(L"[Recording] Unknown exception in save workflow\n");
 5809              hr = E_FAIL;
 5810          }
 5811          if( destFile == nullptr ) {
 5812  
 5813              if (stream) {
 5814                  stream.Close();
 5815                  stream = nullptr;
 5816              }
 5817              try { co_await file.DeleteAsync(); } catch (...) {}
 5818          }
 5819          g_bSaveInProgress = false;
 5820  
 5821          SendMessage( g_hWndMain, WM_USER_RESTORE_CURSOR, 0, 0 );
 5822          if( hWnd == g_hWndMain )
 5823              RestoreForeground();
 5824  
 5825          if( FAILED( hr ) )
 5826              throw winrt::hresult_error( hr );
 5827      }
 5828  
 5829      // Ensure globals are reset after the save/cleanup path completes
 5830      if (stream) {
 5831          stream.Close();
 5832          stream = nullptr;
 5833      }
 5834      g_RecordingSession = nullptr;
 5835      g_GifRecordingSession = nullptr;
 5836  } catch( const winrt::hresult_error& error ) {
 5837  
 5838      // Reset the save-in-progress flag so that hotkeys are not blocked after an error or cancellation
 5839      g_bSaveInProgress = false;
 5840  
 5841      PostMessage( g_hWndMain, WM_USER_STOP_RECORDING, 0, 0 );
 5842  
 5843      // Suppress the error from canceling the save dialog
 5844      if( error.code() == HRESULT_FROM_WIN32( ERROR_CANCELLED ))
 5845          co_return;
 5846  
 5847      if (g_RecordToggle == FALSE) {
 5848  
 5849          MessageBox(g_hWndMain, L"Recording cancelled before started", APPNAME, MB_OK | MB_ICONERROR | MB_SYSTEMMODAL);
 5850      }
 5851      else {
 5852  
 5853          ErrorDialogString(g_hWndMain, L"Error starting recording", error.message().c_str());
 5854      }
 5855  }
 5856  
 5857  //----------------------------------------------------------------------------
 5858  //
 5859  // UpdateMonitorInfo
 5860  //
 5861  //----------------------------------------------------------------------------
 5862  void UpdateMonitorInfo( POINT point, MONITORINFO* monInfo )
 5863  {
 5864      HMONITOR hMon{};
 5865      if( pMonitorFromPoint != nullptr )
 5866      {
 5867          hMon = pMonitorFromPoint( point, MONITOR_DEFAULTTONEAREST );
 5868      }
 5869      if( hMon != nullptr )
 5870      {
 5871          monInfo->cbSize = sizeof *monInfo;
 5872          pGetMonitorInfo( hMon, monInfo );
 5873      }
 5874      else
 5875      {
 5876          *monInfo = {};
 5877          HDC hdcScreen = CreateDC( L"DISPLAY", nullptr, nullptr, nullptr );
 5878          if( hdcScreen != nullptr )
 5879          {
 5880              monInfo->rcMonitor.right = GetDeviceCaps( hdcScreen, HORZRES );
 5881              monInfo->rcMonitor.bottom = GetDeviceCaps( hdcScreen, VERTRES );
 5882              DeleteDC( hdcScreen );
 5883          }
 5884      }
 5885  }
 5886  
 5887  #ifdef __ZOOMIT_POWERTOYS__
 5888  HRESULT OpenPowerToysSettingsApp()
 5889  {
 5890      std::wstring path = get_module_folderpath(g_hInstance);
 5891      path += L"\\PowerToys.exe";
 5892  
 5893      std::wstring openSettings = L"--open-settings=ZoomIt";
 5894  
 5895      std::wstring full_command_path = path + L" " + openSettings;
 5896  
 5897      STARTUPINFO startupInfo;
 5898      ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
 5899      startupInfo.cb = sizeof(STARTUPINFO);
 5900      startupInfo.wShowWindow = SW_SHOWNORMAL;
 5901  
 5902      PROCESS_INFORMATION processInformation;
 5903  
 5904      CreateProcess(
 5905          path.c_str(),
 5906          full_command_path.data(),
 5907          NULL,
 5908          NULL,
 5909          TRUE,
 5910          0,
 5911          NULL,
 5912          NULL,
 5913          &startupInfo,
 5914          &processInformation);
 5915  
 5916      if (!CloseHandle(processInformation.hProcess))
 5917      {
 5918          return HRESULT_FROM_WIN32(GetLastError());
 5919      }
 5920      if (!CloseHandle(processInformation.hThread))
 5921      {
 5922          return HRESULT_FROM_WIN32(GetLastError());
 5923      }
 5924      return S_OK;
 5925  }
 5926  #endif // __ZOOMIT_POWERTOYS__
 5927  
 5928  //----------------------------------------------------------------------------
 5929  //
 5930  // ShowMainWindow
 5931  //
 5932  //----------------------------------------------------------------------------
 5933  void ShowMainWindow(HWND hWnd, const MONITORINFO& monInfo, int width, int height)
 5934  {
 5935      // Show the window first
 5936      SetWindowPos(hWnd, HWND_TOPMOST, monInfo.rcMonitor.left, monInfo.rcMonitor.top,
 5937          width, height, SWP_SHOWWINDOW | SWP_NOCOPYBITS);
 5938  
 5939      // Now invalidate and update the window
 5940      InvalidateRect(hWnd, NULL, TRUE);
 5941      UpdateWindow(hWnd);
 5942  
 5943      SetForegroundWindow(hWnd);
 5944      SetActiveWindow(hWnd);
 5945  }
 5946  
 5947  //----------------------------------------------------------------------------
 5948  //
 5949  // MainWndProc
 5950  //
 5951  //----------------------------------------------------------------------------
 5952  LRESULT APIENTRY MainWndProc(
 5953      HWND hWnd,
 5954      UINT message,
 5955      WPARAM wParam,
 5956      LPARAM lParam)
 5957  {
 5958      static int		width, height;
 5959      static HDC		hdcScreen, hdcScreenCompat, hdcScreenCursorCompat, hdcScreenSaveCompat;
 5960      static HBITMAP	hbmpCompat, hbmpDrawingCompat, hbmpCursorCompat;
 5961      static RECT     cropRc{};
 5962      static BITMAP	bmp;
 5963      static BOOLEAN	g_TimerActive = FALSE;
 5964      static BOOLEAN	g_Zoomed = FALSE;
 5965      static TypeModeState g_TypeMode = TypeModeOff;
 5966      static BOOLEAN	g_HaveTyped = FALSE;
 5967      static DEVMODE	secondaryDevMode;
 5968      static RECT		g_LiveZoomSourceRect;
 5969      static float	g_LiveZoomLevel;
 5970      static float	zoomLevel;
 5971      static float	zoomTelescopeStep;
 5972      static float	zoomTelescopeTarget;
 5973      static POINT	cursorPos;
 5974      static POINT	savedCursorPos;
 5975      static RECT		cursorRc;
 5976      static RECT		boundRc;
 5977      static POINT	prevPt;
 5978      static POINT	textStartPt;
 5979      static POINT	textPt;
 5980      static P_DRAW_UNDO drawUndoList = NULL;
 5981      static P_TYPED_KEY	typedKeyList = NULL;
 5982      static BOOLEAN	g_HaveDrawn = FALSE;
 5983      static DWORD	g_DrawingShape = 0;
 5984      static DWORD    prevPenWidth = g_PenWidth;
 5985      static POINT	g_RectangleAnchor;
 5986      static RECT		g_rcRectangle;
 5987      static BOOLEAN	g_Tracing = FALSE;
 5988      static int		g_BlankedScreen = 0;
 5989      static int		g_StraightDirection = 0;
 5990      static BOOLEAN	g_Drawing = FALSE;
 5991      static HWND		g_ActiveWindow = NULL;
 5992      static int		breakTimeout;
 5993      static HBITMAP	g_hBackgroundBmp = NULL;
 5994      static HDC		g_hDcBackgroundFile;
 5995      static HPEN		hDrawingPen;
 5996      static HFONT	hTimerFont;
 5997      static HFONT	hNegativeTimerFont;
 5998      static HFONT	hTypingFont;
 5999      static MONITORINFO	monInfo;
 6000      static MONITORINFO  lastMonInfo;
 6001      static HWND		hTargetWindow = NULL;
 6002      static RECT		rcTargetWindow;
 6003      static BOOLEAN  forcePenResize = TRUE;
 6004      static BOOLEAN  activeBreakShowDesktop = g_BreakShowDesktop;
 6005      static BOOLEAN	activeBreakShowBackgroundFile = g_BreakShowBackgroundFile;
 6006      static TCHAR    activeBreakBackgroundFile[MAX_PATH] = {0};
 6007      static UINT     wmTaskbarCreated;
 6008  #if 0
 6009      TITLEBARINFO	titleBarInfo;
 6010      WINDOWINFO		targetWindowInfo;
 6011  #endif
 6012      bool			isCaptureSupported = false;
 6013      RECT			rc, rc1;
 6014      PAINTSTRUCT		ps;
 6015      TCHAR			timerText[16];
 6016      TCHAR			negativeTimerText[16];
 6017      BOOLEAN			penInverted;
 6018      BOOLEAN			zoomIn;
 6019      HDC				hDc;
 6020      HWND			hWndRecord;
 6021      int				x, y, delta;
 6022      HMENU			hPopupMenu;
 6023      static TCHAR	filePath[MAX_PATH] = {L"zoomit"};
 6024      NOTIFYICONDATA	tNotifyIconData;
 6025      static DWORD64  g_TelescopingZoomLastTick = 0ull;
 6026  
 6027      const auto drawAllRightJustifiedLines = [&rc]( long lineHeight, bool doPop = false ) {
 6028          rc.top = textPt.y - static_cast<LONG>(g_TextBufferPreviousLines.size()) * lineHeight;
 6029  
 6030          for( const auto& line : g_TextBufferPreviousLines )
 6031          {
 6032              DrawText( hdcScreenCompat, line.c_str(), static_cast<int>(line.length()), &rc, DT_CALCRECT );
 6033              const auto textWidth = rc.right - rc.left;
 6034              rc.left = textPt.x - textWidth;
 6035              rc.right = textPt.x;
 6036              DrawText( hdcScreenCompat, line.c_str(), static_cast<int>(line.length()), &rc, DT_LEFT );
 6037              rc.top += lineHeight;
 6038          }
 6039          if( !g_TextBuffer.empty() )
 6040          {
 6041              if( doPop )
 6042              {
 6043                  g_TextBuffer.pop_back();
 6044              }
 6045              DrawText( hdcScreenCompat, g_TextBuffer.c_str(), static_cast<int>(g_TextBuffer.length()), &rc, DT_CALCRECT );
 6046              rc.left = textPt.x - (rc.right - rc.left);
 6047              rc.right = textPt.x;
 6048              DrawText( hdcScreenCompat, g_TextBuffer.c_str(), static_cast<int>(g_TextBuffer.length()), &rc, DT_LEFT );
 6049          }
 6050      };
 6051  
 6052      const auto doTelescopingZoomTimer = [hWnd, wParam, lParam, &x, &y]( bool invalidate = true ) {
 6053          if( zoomTelescopeStep != 0.0f )
 6054          {
 6055              zoomLevel *= zoomTelescopeStep;
 6056              g_TelescopingZoomLastTick = GetTickCount64();
 6057              if( (zoomTelescopeStep > 1 && zoomLevel >= zoomTelescopeTarget) ||
 6058                  (zoomTelescopeStep < 1 && zoomLevel <= zoomTelescopeTarget) )
 6059              {
 6060                  zoomLevel = zoomTelescopeTarget;
 6061  
 6062                  g_TelescopingZoomLastTick = 0ull;
 6063                  KillTimer( hWnd, wParam );
 6064                  OutputDebug( L"SETCURSOR mon_left: %x mon_top: %x x: %d y: %d\n",
 6065                              monInfo.rcMonitor.left,
 6066                              monInfo.rcMonitor.top,
 6067                              cursorPos.x,
 6068                              cursorPos.y );
 6069                  SetCursorPos( monInfo.rcMonitor.left + cursorPos.x,
 6070                               monInfo.rcMonitor.top + cursorPos.y );
 6071              }
 6072          }
 6073          else
 6074          {
 6075              // Case where we didn't zoom at all
 6076              g_TelescopingZoomLastTick = 0ull;
 6077              KillTimer( hWnd, wParam );
 6078          }
 6079          if( wParam == 2 && zoomLevel == 1 )
 6080          {
 6081              g_Zoomed = FALSE;
 6082  
 6083              // Unregister Ctrl+C and Ctrl+S hotkeys when exiting static zoom
 6084              UnregisterHotKey( hWnd, COPY_IMAGE_HOTKEY );
 6085              UnregisterHotKey( hWnd, COPY_CROP_HOTKEY );
 6086              UnregisterHotKey( hWnd, SAVE_IMAGE_HOTKEY );
 6087              UnregisterHotKey( hWnd, SAVE_CROP_HOTKEY );
 6088  
 6089              if( g_ZoomOnLiveZoom )
 6090              {
 6091                  GetCursorPos( &cursorPos );
 6092                  cursorPos = ScalePointInRects( cursorPos, monInfo.rcMonitor, g_LiveZoomSourceRect );
 6093                  SetCursorPos( cursorPos.x, cursorPos.y );
 6094                  SendMessage( hWnd, WM_HOTKEY, LIVE_HOTKEY, 0 );
 6095              }
 6096              else if( lParam != SHALLOW_ZOOM )
 6097              {
 6098                  // Figure out where final unzoomed cursor should be
 6099                  if( g_Drawing )
 6100                  {
 6101                      cursorPos = prevPt;
 6102                  }
 6103                  OutputDebug( L"FINAL MOUSE: x: %d y: %d\n", cursorPos.x, cursorPos.y );
 6104                  GetZoomedTopLeftCoordinates( zoomLevel, &cursorPos, &x, width, &y, height );
 6105                  cursorPos.x = monInfo.rcMonitor.left + x + static_cast<int>((cursorPos.x - x) * zoomLevel);
 6106                  cursorPos.y = monInfo.rcMonitor.top + y + static_cast<int>((cursorPos.y - y) * zoomLevel);
 6107                  SetCursorPos( cursorPos.x, cursorPos.y );
 6108              }
 6109              if( hTargetWindow )
 6110              {
 6111                  SetWindowPos( hTargetWindow, HWND_BOTTOM, rcTargetWindow.left, rcTargetWindow.top, rcTargetWindow.right - rcTargetWindow.left, rcTargetWindow.bottom - rcTargetWindow.top, 0 );
 6112                  hTargetWindow = NULL;
 6113              }
 6114              DeleteDrawUndoList( &drawUndoList );
 6115  
 6116              // Restore live zoom if we came from that mode
 6117              if( g_ZoomOnLiveZoom )
 6118              {
 6119                  SendMessage( g_hWndLiveZoom, WM_USER_SET_ZOOM, static_cast<WPARAM>(g_LiveZoomLevel), reinterpret_cast<LPARAM>(&g_LiveZoomSourceRect) );
 6120                  g_ZoomOnLiveZoom = FALSE;
 6121                  forcePenResize = TRUE;
 6122              }
 6123  
 6124              SetForegroundWindow( g_ActiveWindow );
 6125              ClipCursor( NULL );
 6126              g_HaveDrawn = FALSE;
 6127              g_TypeMode = TypeModeOff;
 6128              g_HaveTyped = FALSE;
 6129              g_Drawing = FALSE;
 6130              EnableDisableStickyKeys( TRUE );
 6131              DeleteObject( hTypingFont );
 6132              DeleteDC( hdcScreen );
 6133              DeleteDC( hdcScreenCompat );
 6134              DeleteDC( hdcScreenCursorCompat );
 6135              DeleteDC( hdcScreenSaveCompat );
 6136              DeleteObject( hbmpCompat );
 6137              DeleteObject (hbmpCursorCompat );
 6138              DeleteObject( hbmpDrawingCompat );
 6139              DeleteObject( hDrawingPen );
 6140  
 6141              SetFocus( g_ActiveWindow );
 6142              ShowWindow( hWnd, SW_HIDE );
 6143          }
 6144          if( invalidate )
 6145          {
 6146              InvalidateRect( hWnd, NULL, FALSE );
 6147          }
 6148      };
 6149  
 6150      switch (message) {
 6151      case WM_CREATE:
 6152  
 6153          // get default font
 6154          GetObject( GetStockObject(DEFAULT_GUI_FONT), sizeof g_LogFont, &g_LogFont );
 6155          g_LogFont.lfWeight = FW_NORMAL;
 6156          hDc = CreateCompatibleDC( NULL );
 6157          g_LogFont.lfHeight = -MulDiv(8, GetDeviceCaps(hDc, LOGPIXELSY), 72);
 6158          DeleteDC( hDc );
 6159  
 6160          reg.ReadRegSettings( RegSettings );
 6161  
 6162          // Refresh dark mode state after loading theme override from registry
 6163          RefreshDarkModeState();
 6164  
 6165          // Initialize save location strings from registry buffers
 6166          g_RecordingSaveLocation = g_RecordingSaveLocationBuffer;
 6167          g_ScreenshotSaveLocation = g_ScreenshotSaveLocationBuffer;
 6168  
 6169          // Set g_RecordScaling based on the current recording format
 6170          if (g_RecordingFormat == RecordingFormat::GIF) {
 6171              g_RecordScaling = g_RecordScalingGIF;
 6172              g_RecordFrameRate = RECORDING_FORMAT_GIF_DEFAULT_FRAMERATE;
 6173          } else {
 6174              g_RecordScaling = g_RecordScalingMP4;
 6175              g_RecordFrameRate = RECORDING_FORMAT_MP4_DEFAULT_FRAMERATE;
 6176          }
 6177  
 6178          // to support migrating from
 6179          if ((g_PenColor >> 24) == 0) {
 6180              g_PenColor |= 0xFF << 24;
 6181          }
 6182  
 6183          g_PenWidth = g_RootPenWidth;
 6184  
 6185          g_ToggleMod = GetKeyMod( g_ToggleKey );
 6186          g_LiveZoomToggleMod = GetKeyMod( g_LiveZoomToggleKey );
 6187          g_DrawToggleMod = GetKeyMod( g_DrawToggleKey );
 6188          g_BreakToggleMod = GetKeyMod( g_BreakToggleKey );
 6189          g_DemoTypeToggleMod = GetKeyMod( g_DemoTypeToggleKey );
 6190          g_SnipToggleMod = GetKeyMod( g_SnipToggleKey );
 6191          g_RecordToggleMod = GetKeyMod( g_RecordToggleKey );
 6192  
 6193          if( !g_OptionsShown && !g_StartedByPowerToys ) {
 6194              // First run should show options when running as standalone. If not running as standalone,
 6195              // options screen won't show and we should register keys instead.
 6196              SendMessage( hWnd, WM_COMMAND, IDC_OPTIONS, 0 );
 6197              g_OptionsShown = TRUE;
 6198              reg.WriteRegSettings( RegSettings );
 6199          } else {
 6200              BOOL	showOptions = FALSE;
 6201  
 6202              if( g_ToggleKey && !RegisterHotKey( hWnd, ZOOM_HOTKEY, g_ToggleMod, g_ToggleKey & 0xFF)) {
 6203  
 6204                  MessageBox( hWnd, L"The specified zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.",
 6205                      APPNAME, MB_ICONERROR );
 6206                  showOptions = TRUE;
 6207  
 6208              } else if( g_LiveZoomToggleKey &&
 6209                  (!RegisterHotKey( hWnd, LIVE_HOTKEY, g_LiveZoomToggleMod, g_LiveZoomToggleKey & 0xFF) ||
 6210                      !RegisterHotKey(hWnd, LIVE_DRAW_HOTKEY, (g_LiveZoomToggleMod ^ MOD_SHIFT), g_LiveZoomToggleKey & 0xFF))) {
 6211  
 6212                  MessageBox( hWnd, L"The specified live-zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.",
 6213                      APPNAME, MB_ICONERROR );
 6214                  showOptions = TRUE;
 6215  
 6216              } else if( g_DrawToggleKey && !RegisterHotKey( hWnd, DRAW_HOTKEY, g_DrawToggleMod, g_DrawToggleKey & 0xFF )) {
 6217  
 6218                  MessageBox( hWnd, L"The specified draw w/out zoom hotkey is already in use.\nSelect a different draw w/out zoom hotkey.",
 6219                      APPNAME, MB_ICONERROR );
 6220                  showOptions = TRUE;
 6221  
 6222              }
 6223              else if (g_BreakToggleKey && !RegisterHotKey(hWnd, BREAK_HOTKEY, g_BreakToggleMod, g_BreakToggleKey & 0xFF)) {
 6224  
 6225                  MessageBox(hWnd, L"The specified break timer hotkey is already in use.\nSelect a different break timer hotkey.",
 6226                      APPNAME, MB_ICONERROR);
 6227                  showOptions = TRUE;
 6228  
 6229              }
 6230              else if( g_DemoTypeToggleKey &&
 6231                  (!RegisterHotKey( hWnd, DEMOTYPE_HOTKEY, g_DemoTypeToggleMod, g_DemoTypeToggleKey & 0xFF ) ||
 6232                   !RegisterHotKey(hWnd, DEMOTYPE_RESET_HOTKEY, (g_DemoTypeToggleMod ^ MOD_SHIFT), g_DemoTypeToggleKey & 0xFF))) {
 6233  
 6234                  MessageBox( hWnd, L"The specified live-type hotkey is already in use.\nSelect a different live-type hotkey.",
 6235                      APPNAME, MB_ICONERROR );
 6236                  showOptions = TRUE;
 6237  
 6238              }
 6239              else if (g_SnipToggleKey &&
 6240                  (!RegisterHotKey(hWnd, SNIP_HOTKEY, g_SnipToggleMod, g_SnipToggleKey & 0xFF) ||
 6241                   !RegisterHotKey(hWnd, SNIP_SAVE_HOTKEY, (g_SnipToggleMod ^ MOD_SHIFT), g_SnipToggleKey & 0xFF))) {
 6242  
 6243                  MessageBox(hWnd, L"The specified snip hotkey is already in use.\nSelect a different snip hotkey.",
 6244                      APPNAME, MB_ICONERROR);
 6245                  showOptions = TRUE;
 6246  
 6247              }
 6248              else if (g_RecordToggleKey &&
 6249                  (!RegisterHotKey(hWnd, RECORD_HOTKEY, g_RecordToggleMod | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) ||
 6250                   !RegisterHotKey(hWnd, RECORD_CROP_HOTKEY, (g_RecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) ||
 6251                   !RegisterHotKey(hWnd, RECORD_WINDOW_HOTKEY, (g_RecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF))) {
 6252  
 6253                  MessageBox(hWnd, L"The specified record hotkey is already in use.\nSelect a different record hotkey.",
 6254                      APPNAME, MB_ICONERROR);
 6255                  showOptions = TRUE;
 6256              }
 6257              if( showOptions ) {
 6258  
 6259                  SendMessage( hWnd, WM_COMMAND, IDC_OPTIONS, 0 );
 6260              }
 6261          }
 6262          SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL );
 6263          wmTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
 6264          return TRUE;
 6265  
 6266      case WM_CLOSE:
 6267          // Do not allow users to close the main window, for example with Alt-F4.
 6268          return 0;
 6269  
 6270      case WM_HOTKEY:
 6271          if( g_RecordCropping == TRUE )
 6272          {
 6273              if( wParam != RECORD_CROP_HOTKEY )
 6274              {
 6275                  // Cancel cropping on any hotkey.
 6276                  g_SelectRectangle.Stop();
 6277                  g_RecordCropping = FALSE;
 6278  
 6279                  // Cropping is handled by a blocking call in WM_HOTKEY, so post
 6280                  // this message to the window for processing after the previous
 6281                  // WM_HOTKEY message completes processing.
 6282                  PostMessage( hWnd, message, wParam, lParam );
 6283              }
 6284              return 0;
 6285          }
 6286  
 6287          //
 6288          // Magic value that comes from tray context menu
 6289          //
 6290          if (lParam == 1) {
 6291  
 6292              //
 6293              // Sleep to let context menu dismiss
 6294              //
 6295              Sleep(250);
 6296          }
 6297          switch( wParam ) {
 6298          case LIVE_DRAW_HOTKEY:
 6299          {
 6300              OutputDebug(L"LIVE_DRAW_HOTKEY\n");
 6301              LONG_PTR exStyle = GetWindowLongPtr(hWnd, GWL_EXSTYLE);
 6302  
 6303              if ((exStyle & WS_EX_LAYERED)) {
 6304                  OutputDebug(L"LiveDraw reactivate\n");
 6305  
 6306                  // Just focus on the window and re-enter drawing mode
 6307                  SetFocus(hWnd);
 6308                  SetForegroundWindow(hWnd);
 6309                  SendMessage(hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM(cursorPos.x, cursorPos.y));
 6310                  SendMessage(hWnd, WM_MOUSEMOVE, 0, MAKELPARAM(cursorPos.x, cursorPos.y));
 6311                  if( IsWindowVisible( g_hWndLiveZoom ) )
 6312                  {
 6313                      SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, FALSE, 0 );
 6314                  }
 6315                  break;
 6316              }
 6317              else {
 6318                  OutputDebug(L"LiveDraw create\n");
 6319  
 6320                  exStyle = GetWindowLongPtr(hWnd, GWL_EXSTYLE);
 6321                  SetWindowLongPtr(hWnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED);
 6322                  SetLayeredWindowAttributes(hWnd, COLORREF(RGB(0, 0, 0)), 0, LWA_COLORKEY);
 6323                  pMagSetWindowFilterList( g_hWndLiveZoomMag, MW_FILTERMODE_EXCLUDE, 0, nullptr );
 6324              }
 6325              [[fallthrough]];
 6326          }
 6327          case DRAW_HOTKEY:
 6328              //
 6329              // Enter drawing mode without zoom
 6330              //
 6331  #ifdef __ZOOMIT_POWERTOYS__
 6332              if( g_StartedByPowerToys )
 6333              {
 6334                  Trace::ZoomItActivateDraw();
 6335              }
 6336  #endif // __ZOOMIT_POWERTOYS__
 6337  
 6338              if( !g_Zoomed ) {
 6339                  OutputDebug(L"LiveDraw: %d (%d)\n", wParam, (wParam == LIVE_DRAW_HOTKEY));
 6340  
 6341  #if WINDOWS_CURSOR_RECORDING_WORKAROUND
 6342                  if( IsWindowVisible( g_hWndLiveZoom ) && !g_LiveZoomLevelOne ) {
 6343  #else
 6344                  if( IsWindowVisible( g_hWndLiveZoom )) {
 6345  #endif
 6346  
 6347                      OutputDebug(L"   In Live zoom\n");
 6348                      SendMessage(hWnd, WM_HOTKEY, ZOOM_HOTKEY, wParam == LIVE_DRAW_HOTKEY ? LIVE_DRAW_ZOOM : 0);
 6349  
 6350                  } else {
 6351                      OutputDebug(L"   Not in Live zoom\n");
 6352                      SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, wParam == LIVE_DRAW_HOTKEY ? LIVE_DRAW_ZOOM : 0 );
 6353                      zoomLevel = zoomTelescopeTarget = 1;
 6354                      SendMessage( hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM( cursorPos.x, cursorPos.y ));
 6355                  }
 6356                  if(wParam == LIVE_DRAW_HOTKEY) {
 6357  
 6358                      SetLayeredWindowAttributes(hWnd, COLORREF(RGB(0, 0, 0)), 0, LWA_COLORKEY);
 6359                      SendMessage(hWnd, WM_KEYDOWN, 'K', LIVE_DRAW_ZOOM);
 6360                      SetTimer(hWnd, 3, 10, NULL);
 6361                      SendMessage(hWnd, WM_MOUSEMOVE, 0, MAKELPARAM(cursorPos.x, cursorPos.y));
 6362                      ShowMainWindow(hWnd, monInfo, width, height);
 6363                      if( ( g_PenColor & 0xFFFFFF ) == COLOR_BLUR )
 6364                      {
 6365                          // Blur is not supported in LiveDraw
 6366                          g_PenColor = COLOR_RED;
 6367                      }
 6368                      // Highlight is not supported in LiveDraw
 6369                      g_PenColor |= 0xFF << 24;
 6370  				}
 6371              }
 6372              break;
 6373  
 6374          case SNIP_SAVE_HOTKEY:
 6375          case SNIP_HOTKEY:
 6376          {
 6377              OutputDebugStringW((L"[Snip] Hotkey received: " + std::to_wstring(LOWORD(wParam)) +
 6378                  L" (SNIP_SAVE=" + std::to_wstring(SNIP_SAVE_HOTKEY) +
 6379                  L" SNIP=" + std::to_wstring(SNIP_HOTKEY) + L")\n").c_str());
 6380  
 6381              // Block liveZoom liveDraw snip due to mirroring bug
 6382              if( IsWindowVisible( g_hWndLiveZoom )
 6383                  && ( GetWindowLongPtr( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) )
 6384              {
 6385                  break;
 6386              }
 6387  
 6388              bool zoomed = true;
 6389  #ifdef __ZOOMIT_POWERTOYS__
 6390              if( g_StartedByPowerToys )
 6391              {
 6392                  Trace::ZoomItActivateSnip();
 6393              }
 6394  #endif // __ZOOMIT_POWERTOYS__
 6395  
 6396              // First, static zoom
 6397              if( !g_Zoomed )
 6398              {
 6399                  zoomed = false;
 6400                  if( IsWindowVisible( g_hWndLiveZoom ) && !g_LiveZoomLevelOne )
 6401                  {
 6402                      SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_ZOOM );
 6403                  }
 6404                  else
 6405                  {
 6406                      SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, LIVE_DRAW_ZOOM);
 6407                  }
 6408                  zoomLevel = zoomTelescopeTarget = 1;
 6409              }
 6410              else if( g_Drawing )
 6411              {
 6412                  // Exit drawing mode to hide the drawing cursor
 6413                  SendMessage( hWnd, WM_USER_EXIT_MODE, 0, 0 );
 6414  
 6415                  // Exit again if still in drawing mode, which happens from type mode
 6416                  if( g_Drawing )
 6417                  {
 6418                      SendMessage( hWnd, WM_USER_EXIT_MODE, 0, 0 );
 6419                  }
 6420              }
 6421              ShowMainWindow(hWnd, monInfo, width, height);
 6422  
 6423              // Now copy crop or copy+save
 6424              if( LOWORD( wParam ) == SNIP_SAVE_HOTKEY )
 6425              {
 6426                  // IDC_SAVE_CROP handles cursor hiding internally after region selection
 6427                  SendMessage( hWnd, WM_COMMAND, IDC_SAVE_CROP, ( zoomed ? 0 : SHALLOW_ZOOM ) );
 6428              }
 6429              else
 6430              {
 6431                  SendMessage( hWnd, WM_COMMAND, IDC_COPY_CROP, ( zoomed ? 0 : SHALLOW_ZOOM ) );
 6432              }
 6433  
 6434              // Now if we weren't zoomed, unzoom
 6435              if( !zoomed )
 6436              {
 6437                  if( g_ZoomOnLiveZoom )
 6438                  {
 6439                      // hiding the cursor allows for a cleaner transition back to the magnified cursor
 6440                      ShowCursor( false );
 6441                      SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 );
 6442                      ShowCursor( true );
 6443                  }
 6444                  else
 6445                  {
 6446                      SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_ZOOM );
 6447                  }
 6448              }
 6449  
 6450              // exit zoom
 6451              if( g_Zoomed )
 6452              {
 6453                  // If from liveDraw, extra care is needed to destruct
 6454                  if( GetWindowLong( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED )
 6455                  {
 6456                      OutputDebug( L"Exiting liveDraw after snip\n" );
 6457                      SendMessage( hWnd, WM_KEYDOWN, VK_ESCAPE, 0 );
 6458                  }
 6459              }
 6460              break;
 6461          }
 6462  
 6463          case SAVE_IMAGE_HOTKEY:
 6464              SendMessage(hWnd, WM_COMMAND, IDC_SAVE, 0);
 6465              break;
 6466  
 6467          case SAVE_CROP_HOTKEY:
 6468              SendMessage(hWnd, WM_COMMAND, IDC_SAVE_CROP, 0);
 6469              break;
 6470  
 6471          case COPY_IMAGE_HOTKEY:
 6472              SendMessage(hWnd, WM_COMMAND, IDC_COPY, 0);
 6473              break;
 6474  
 6475          case COPY_CROP_HOTKEY:
 6476              SendMessage(hWnd, WM_COMMAND, IDC_COPY_CROP, 0);
 6477              break;
 6478  
 6479          case BREAK_HOTKEY:
 6480              //
 6481              // Go to break timer
 6482              //
 6483  #if WINDOWS_CURSOR_RECORDING_WORKAROUND
 6484              if( !g_Zoomed && ( !IsWindowVisible( g_hWndLiveZoom ) || g_LiveZoomLevelOne ) ) {
 6485  #else
 6486              if( !g_Zoomed && !IsWindowVisible( g_hWndLiveZoom )) {
 6487  #endif
 6488  
 6489                  SendMessage( hWnd, WM_COMMAND, IDC_BREAK, 0 );
 6490              }
 6491              break;
 6492  
 6493          case DEMOTYPE_RESET_HOTKEY:
 6494              ResetDemoTypeIndex();
 6495              break;
 6496  
 6497          case DEMOTYPE_HOTKEY:
 6498          {
 6499              //
 6500              // Live type
 6501              //
 6502              switch( StartDemoType( g_DemoTypeFile, g_DemoTypeSpeedSlider, g_DemoTypeUserDriven ) )
 6503              {
 6504              case ERROR_LOADING_FILE:
 6505                  ErrorDialog( hWnd, L"Error loading DemoType file", GetLastError() );
 6506                  break;
 6507  
 6508              case NO_FILE_SPECIFIED:
 6509                  MessageBox( hWnd, L"No DemoType file specified", APPNAME, MB_OK );
 6510                  break;
 6511  
 6512              case FILE_SIZE_OVERFLOW:
 6513              {
 6514                  std::wstring msg = L"Unsupported DemoType file size ("
 6515                      + std::to_wstring( MAX_INPUT_SIZE ) + L" byte limit)";
 6516                  MessageBox( hWnd, msg.c_str(), APPNAME, MB_OK );
 6517                  break;
 6518              }
 6519  
 6520              case UNKNOWN_FILE_DATA:
 6521                  MessageBox( hWnd, L"Unrecognized DemoType file content", APPNAME, MB_OK );
 6522                  break;
 6523              default:
 6524  #ifdef __ZOOMIT_POWERTOYS__
 6525                  if( g_StartedByPowerToys )
 6526                  {
 6527                      Trace::ZoomItActivateDemoType();
 6528                  }
 6529  #endif // __ZOOMIT_POWERTOYS__
 6530                  break;
 6531              }
 6532              break;
 6533          }
 6534  
 6535          case LIVE_HOTKEY:
 6536              //
 6537              // Live zoom
 6538              //
 6539              OutputDebug(L"*** LIVE_HOTKEY\n");
 6540  
 6541              // If LiveZoom and LiveDraw are active then exit both
 6542              if( g_Zoomed && IsWindowVisible( g_hWndLiveZoom ) && ( GetWindowLongPtr( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) )
 6543              {
 6544                  SendMessage( hWnd, WM_KEYDOWN, VK_ESCAPE, 0 );
 6545                  PostMessage(hWnd, WM_HOTKEY, LIVE_HOTKEY, 0);
 6546                  break;
 6547              }
 6548  
 6549              if( !g_Zoomed && !g_TimerActive && ( !g_fullScreenWorkaround || !g_RecordToggle ) ) {
 6550  #ifdef __ZOOMIT_POWERTOYS__
 6551                  if( g_StartedByPowerToys )
 6552                  {
 6553                      Trace::ZoomItActivateLiveZoom();
 6554                  }
 6555  #endif // __ZOOMIT_POWERTOYS__
 6556  
 6557                  if( g_hWndLiveZoom == NULL ) {
 6558                      OutputDebug(L"Create LIVEZOOM\n");
 6559                      g_hWndLiveZoom = CreateWindowEx( WS_EX_TOOLWINDOW | WS_EX_LAYERED | WS_EX_TRANSPARENT,
 6560                          L"MagnifierClass", L"ZoomIt Live Zoom",
 6561                          WS_POPUP | WS_CLIPSIBLINGS,
 6562                          0, 0, 0, 0, NULL, NULL, g_hInstance, static_cast<PVOID>(GetForegroundWindow()) );
 6563                      pSetLayeredWindowAttributes( hWnd, 0, 0, LWA_ALPHA );
 6564                      EnableWindow( g_hWndLiveZoom, FALSE );
 6565                      pMagSetWindowFilterList( g_hWndLiveZoomMag, MW_FILTERMODE_EXCLUDE, 1, &hWnd );
 6566  
 6567                  } else {
 6568  #if WINDOWS_CURSOR_RECORDING_WORKAROUND
 6569                      if( g_LiveZoomLevelOne ) {
 6570                          OutputDebug(L"liveZoom level one\n");
 6571                          SendMessage( g_hWndLiveZoom, WM_USER_SET_ZOOM, static_cast<WPARAM>(g_LiveZoomLevel), 0 );
 6572                      }
 6573                      else {
 6574  #endif
 6575  
 6576                      if( IsWindowVisible( g_hWndLiveZoom )) {
 6577  #if WINDOWS_CURSOR_RECORDING_WORKAROUND
 6578  
 6579                          if( g_RecordToggle )
 6580                              g_LiveZoomLevel = g_ZoomLevels[g_SliderZoomLevel];
 6581  #endif
 6582                          // Unzoom
 6583                          SendMessage( g_hWndLiveZoom, WM_KEYDOWN, VK_ESCAPE, 0 );
 6584  
 6585                      } else {
 6586  
 6587                          OutputDebug(L"Show liveZoom\n");
 6588                          ShowWindow( g_hWndLiveZoom, SW_SHOW );
 6589                      }
 6590  #if WINDOWS_CURSOR_RECORDING_WORKAROUND
 6591                      }
 6592  #endif
 6593                  }
 6594                  OutputDebug(L"LIVEDRAW SMOOTHING: %d\n", g_SmoothImage);
 6595                  if (!pMagSetLensUseBitmapSmoothing(g_hWndLiveZoomMag, g_SmoothImage))
 6596                  {
 6597                      OutputDebug(L"MagSetLensUseBitmapSmoothing failed: %d\n", GetLastError());
 6598                  }
 6599  
 6600                  if ( g_RecordToggle )
 6601                  {
 6602                      g_SelectRectangle.UpdateOwner( g_hWndLiveZoom );
 6603                  }
 6604              }
 6605              break;
 6606  
 6607          case RECORD_HOTKEY:
 6608          case RECORD_CROP_HOTKEY:
 6609          case RECORD_WINDOW_HOTKEY:
 6610          case RECORD_GIF_HOTKEY:
 6611          case RECORD_GIF_WINDOW_HOTKEY:
 6612  
 6613              //
 6614              // Recording
 6615              // This gets entered twice per recording:
 6616              // 1. When the hotkey is pressed to start recording
 6617              // 2. When the hotkey is pressed to stop recording
 6618              //
 6619              if( g_fullScreenWorkaround && g_hWndLiveZoom != NULL && IsWindowVisible( g_hWndLiveZoom ) != FALSE )
 6620              {
 6621                  break;
 6622              }
 6623  
 6624              if( g_RecordCropping == TRUE )
 6625              {
 6626                  break;
 6627              }
 6628  
 6629              // Ignore recording hotkey when save dialog is open
 6630              if( g_bSaveInProgress )
 6631              {
 6632                  break;
 6633              }
 6634  
 6635              // Start screen recording
 6636              try
 6637              {
 6638                  isCaptureSupported = winrt::GraphicsCaptureSession::IsSupported();
 6639              }
 6640              catch( const winrt::hresult_error& ) {}
 6641              if( !isCaptureSupported )
 6642              {
 6643                  MessageBox( hWnd, L"Screen recording requires Windows 10, May 2019 Update or higher.", APPNAME, MB_OK );
 6644                  break;
 6645              }
 6646  
 6647              // If shift, then we're cropping
 6648              hWndRecord = 0;
 6649              if( wParam == RECORD_CROP_HOTKEY )
 6650              {
 6651                  if( g_RecordToggle == TRUE )
 6652                  {
 6653                      // Already recording
 6654                      break;
 6655                  }
 6656  
 6657                  g_RecordCropping = TRUE;
 6658  
 6659                  POINT savedPoint{};
 6660                  RECT savedClip = {};
 6661  
 6662                  // Handle the cursor for live zoom and static zoom modes.
 6663                  if( ( g_hWndLiveZoom != nullptr ) || ( g_Zoomed == TRUE ) )
 6664                  {
 6665                      GetCursorPos( &savedPoint );
 6666                      UpdateMonitorInfo( savedPoint, &monInfo );
 6667                  }
 6668                  if( g_hWndLiveZoom != nullptr )
 6669                  {
 6670                      // Hide the magnified cursor.
 6671                      SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, FALSE, 0 );
 6672  
 6673                      // Show the system cursor where the magnified was.
 6674                      g_LiveZoomSourceRect = *reinterpret_cast<RECT *>( SendMessage( g_hWndLiveZoom, WM_USER_GET_SOURCE_RECT, 0, 0 ) );
 6675                      savedPoint = ScalePointInRects( savedPoint, g_LiveZoomSourceRect, monInfo.rcMonitor );
 6676                      SetCursorPos( savedPoint.x, savedPoint.y );
 6677                      if ( pMagShowSystemCursor != nullptr )
 6678                      {
 6679                          pMagShowSystemCursor( TRUE );
 6680                      }
 6681                  }
 6682                  else if( ( g_Zoomed == TRUE ) && ( g_Drawing == TRUE ) )
 6683                  {
 6684                      // Unclip the cursor.
 6685                      GetClipCursor( &savedClip );
 6686                      ClipCursor( nullptr );
 6687  
 6688                      // Scale the cursor position to the zoomed and move it.
 6689                      auto point = ScalePointInRects( savedPoint, boundRc, monInfo.rcMonitor );
 6690                      SetCursorPos( point.x, point.y );
 6691                  }
 6692  
 6693                  if( g_Zoomed == FALSE )
 6694                  {
 6695                      SetWindowPos( hWnd, HWND_TOPMOST, monInfo.rcMonitor.left, monInfo.rcMonitor.top, width, height, SWP_SHOWWINDOW );
 6696                  }
 6697  
 6698                  // This call blocks with a message loop while cropping.
 6699                  auto canceled = !g_SelectRectangle.Start( ( g_hWndLiveZoom != nullptr ) ? g_hWndLiveZoom : hWnd );
 6700                  g_RecordCropping = FALSE;
 6701  
 6702                  // Restore the cursor if applicable.
 6703                  if( g_hWndLiveZoom != nullptr )
 6704                  {
 6705                      // Hide the system cursor.
 6706                      if ( pMagShowSystemCursor != nullptr )
 6707                      {
 6708                          pMagShowSystemCursor( FALSE );
 6709                      }
 6710  
 6711                      // Show the magnified cursor where the system cursor was.
 6712                      GetCursorPos( &savedPoint );
 6713                      savedPoint = ScalePointInRects( savedPoint, monInfo.rcMonitor, g_LiveZoomSourceRect );
 6714                      SetCursorPos( savedPoint.x, savedPoint.y );
 6715                      SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, TRUE, 0 );
 6716                  }
 6717                  else if( g_Zoomed == TRUE )
 6718                  {
 6719                      SetCursorPos( savedPoint.x, savedPoint.y );
 6720  
 6721                      if ( g_Drawing == TRUE )
 6722                      {
 6723                          ClipCursor( &savedClip );
 6724                      }
 6725                  }
 6726  
 6727                  SetForegroundWindow( hWnd );
 6728                  if( g_Zoomed == FALSE )
 6729                  {
 6730                      SetActiveWindow( hWnd );
 6731                      ShowWindow( hWnd, SW_HIDE );
 6732                  }
 6733  
 6734                  if( canceled )
 6735                  {
 6736                      break;
 6737                  }
 6738  
 6739                  g_SelectRectangle.UpdateOwner( ( g_hWndLiveZoom != nullptr ) ? g_hWndLiveZoom : hWnd );
 6740                  cropRc = g_SelectRectangle.SelectedRect();
 6741              }
 6742              else
 6743              {
 6744                  cropRc = {};
 6745  
 6746                  // if we're recording a window, get the window
 6747                  if (wParam == RECORD_WINDOW_HOTKEY || wParam == RECORD_GIF_WINDOW_HOTKEY)
 6748                  {
 6749                      GetCursorPos(&cursorPos);
 6750                      hWndRecord = WindowFromPoint(cursorPos);
 6751                      while( GetParent(hWndRecord) != NULL)
 6752                      {
 6753                          hWndRecord = GetParent(hWndRecord);
 6754                      }
 6755                      if( hWndRecord == GetDesktopWindow()) {
 6756  
 6757                          hWndRecord = NULL;
 6758                      }
 6759                  }
 6760              }
 6761  
 6762              if( g_RecordToggle == FALSE )
 6763              {
 6764                  g_RecordToggle = TRUE;
 6765  
 6766  #ifdef __ZOOMIT_POWERTOYS__
 6767                  if( g_StartedByPowerToys )
 6768                  {
 6769                      Trace::ZoomItActivateRecord();
 6770                  }
 6771  #endif // __ZOOMIT_POWERTOYS__
 6772  
 6773                  StartRecordingAsync( hWnd, &cropRc, hWndRecord );
 6774              }
 6775              else
 6776              {
 6777                  StopRecording();
 6778              }
 6779              break;
 6780  
 6781          case ZOOM_HOTKEY:
 6782              //
 6783              // Zoom
 6784              //
 6785              // Don't react to hotkey while options are open or we're
 6786              // saving the screen or live zoom is active
 6787              //
 6788              if( hWndOptions ) {
 6789  
 6790                  break;
 6791              }
 6792  
 6793              OutputDebug( L"ZOOM HOTKEY: %d\n", lParam);
 6794              if( g_TimerActive ) {
 6795  
 6796                  //
 6797                  // Finished with break timer
 6798                  //
 6799                  if( g_BreakOnSecondary )
 6800                  {
 6801                      EnableDisableSecondaryDisplay( hWnd, FALSE, &secondaryDevMode );
 6802                  }
 6803  
 6804                  if( lParam != SHALLOW_DESTROY )
 6805                  {
 6806                      ShowWindow( hWnd, SW_HIDE );
 6807                      if( g_hBackgroundBmp )
 6808                      {
 6809                          DeleteObject( g_hBackgroundBmp );
 6810                          DeleteDC( g_hDcBackgroundFile );
 6811                          g_hBackgroundBmp = NULL;
 6812                      }
 6813                  }
 6814  
 6815                  SetFocus( GetDesktopWindow() );
 6816                  KillTimer( hWnd, 0 );
 6817                  g_TimerActive = FALSE;
 6818  
 6819                  DeleteObject( hTimerFont );
 6820                  DeleteObject( hNegativeTimerFont );
 6821                  DeleteDC( hdcScreen );
 6822                  DeleteDC( hdcScreenCompat );
 6823                  DeleteDC( hdcScreenSaveCompat );
 6824                  DeleteDC( hdcScreenCursorCompat );
 6825                  DeleteObject( hbmpCompat );
 6826                  EnableDisableScreenSaver( TRUE );
 6827                  EnableDisableOpacity( hWnd, FALSE );
 6828  
 6829              } else {
 6830  
 6831                  SendMessage( hWnd, WM_USER_TYPING_OFF, 0, 0 );
 6832                  if( !g_Zoomed ) {
 6833  
 6834                      g_Zoomed = TRUE;
 6835                      g_DrawingShape = FALSE;
 6836                      OutputDebug( L"Zoom on\n");
 6837  
 6838                      // Register Ctrl+C and Ctrl+S hotkeys only during static zoom
 6839                      RegisterHotKey(hWnd, COPY_IMAGE_HOTKEY, MOD_CONTROL | MOD_NOREPEAT, 'C');
 6840                      RegisterHotKey(hWnd, COPY_CROP_HOTKEY, MOD_CONTROL | MOD_SHIFT | MOD_NOREPEAT, 'C');
 6841                      RegisterHotKey(hWnd, SAVE_IMAGE_HOTKEY, MOD_CONTROL | MOD_NOREPEAT, 'S');
 6842                      RegisterHotKey(hWnd, SAVE_CROP_HOTKEY, MOD_CONTROL | MOD_SHIFT | MOD_NOREPEAT, 'S');
 6843  
 6844  #ifdef __ZOOMIT_POWERTOYS__
 6845                      if( g_StartedByPowerToys )
 6846                      {
 6847                          Trace::ZoomItActivateZoom();
 6848                      }
 6849  #endif // __ZOOMIT_POWERTOYS__
 6850  
 6851                      // Hide the cursor before capturing if in live zoom
 6852                      if( g_hWndLiveZoom != nullptr )
 6853                      {
 6854                          OutputDebug(L"Hide cursor\n");
 6855                          SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, FALSE, 0 );
 6856                          SendMessage( g_hWndLiveZoom, WM_TIMER, 0, 0 );
 6857                          SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, FALSE, 0 );
 6858                      }
 6859  
 6860                      // Get screen DCs
 6861                      hdcScreen = CreateDC(L"DISPLAY", static_cast<PTCHAR>(NULL),
 6862                              static_cast<PTCHAR>(NULL), static_cast<CONST DEVMODE *>(NULL));
 6863                      hdcScreenCompat = CreateCompatibleDC(hdcScreen);
 6864                      hdcScreenSaveCompat = CreateCompatibleDC(hdcScreen);
 6865                      hdcScreenCursorCompat = CreateCompatibleDC(hdcScreen);
 6866  
 6867                      // Determine what monitor we're on
 6868                      GetCursorPos(&cursorPos);
 6869                      UpdateMonitorInfo( cursorPos, &monInfo );
 6870                      width = monInfo.rcMonitor.right - monInfo.rcMonitor.left;
 6871                      height = monInfo.rcMonitor.bottom - monInfo.rcMonitor.top;
 6872                      OutputDebug( L"ZOOM x: %d y: %d width: %d height: %d zoomLevel: %g\n",
 6873                              cursorPos.x, cursorPos.y, width, height, zoomLevel );
 6874  
 6875                      // Create display bitmap
 6876                      bmp.bmBitsPixel = static_cast<BYTE>(GetDeviceCaps(hdcScreen, BITSPIXEL));
 6877                      bmp.bmPlanes = static_cast<BYTE>(GetDeviceCaps(hdcScreen, PLANES));
 6878                      bmp.bmWidth = width;
 6879                      bmp.bmHeight = height;
 6880                      bmp.bmWidthBytes = ((bmp.bmWidth + 15) &~15)/8;
 6881                      hbmpCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
 6882                          bmp.bmPlanes, bmp.bmBitsPixel, static_cast<CONST VOID *>(NULL));
 6883                       SelectObject(hdcScreenCompat, hbmpCompat);
 6884  
 6885                      // Create saved bitmap
 6886                      hbmpDrawingCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
 6887                          bmp.bmPlanes, bmp.bmBitsPixel, static_cast<CONST VOID *>(NULL));
 6888                      SelectObject(hdcScreenSaveCompat, hbmpDrawingCompat);
 6889  
 6890                      // Create cursor save bitmap
 6891                      // (have to accomodate large fonts and LiveZoom pen scaling)
 6892                      hbmpCursorCompat = CreateBitmap( MAX_LIVE_PEN_WIDTH+CURSOR_ARM_LENGTH*2,
 6893                          MAX_LIVE_PEN_WIDTH+CURSOR_ARM_LENGTH*2, bmp.bmPlanes,
 6894                          bmp.bmBitsPixel, static_cast<CONST VOID *>(NULL));
 6895                      SelectObject(hdcScreenCursorCompat, hbmpCursorCompat);
 6896  
 6897                      // Create typing font
 6898                      g_LogFont.lfHeight = height / 15;
 6899                      if (g_LogFont.lfHeight < 20)
 6900                          g_LogFont.lfQuality = NONANTIALIASED_QUALITY;
 6901                      else
 6902                          g_LogFont.lfQuality = ANTIALIASED_QUALITY;
 6903                      hTypingFont = CreateFontIndirect(&g_LogFont);
 6904                      SelectObject(hdcScreenCompat, hTypingFont);
 6905                      SetTextColor(hdcScreenCompat, g_PenColor & 0xFFFFFF);
 6906                      SetBkMode(hdcScreenCompat, TRANSPARENT);
 6907  
 6908                      // Use the screen DC unless recording, because it contains the yellow border
 6909                      HDC hdcSource = hdcScreen;
 6910                      if( g_RecordToggle ) try {
 6911  
 6912                          auto capture = CaptureScreenshot( winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized );
 6913                          auto bytes = CopyBytesFromTexture( capture );
 6914  
 6915                          D3D11_TEXTURE2D_DESC desc;
 6916                          capture->GetDesc( &desc );
 6917                          BITMAPINFO bitmapInfo = {};
 6918                          bitmapInfo.bmiHeader.biSize = sizeof bitmapInfo.bmiHeader;
 6919                          bitmapInfo.bmiHeader.biWidth = desc.Width;
 6920                          bitmapInfo.bmiHeader.biHeight = -static_cast<LONG>(desc.Height);
 6921                          bitmapInfo.bmiHeader.biPlanes = 1;
 6922                          bitmapInfo.bmiHeader.biBitCount = 32;
 6923                          bitmapInfo.bmiHeader.biCompression = BI_RGB;
 6924                          void *bits;
 6925                          auto dib = CreateDIBSection( NULL, &bitmapInfo, DIB_RGB_COLORS, &bits, nullptr, 0 );
 6926                          if( dib ) {
 6927  
 6928                              CopyMemory( bits, bytes.data(), bytes.size() );
 6929                              auto hdcCapture = CreateCompatibleDC( hdcScreen );
 6930                              SelectObject( hdcCapture, dib );
 6931                              hdcSource = hdcCapture;
 6932                          }
 6933  
 6934                      } catch( const winrt::hresult_error& ) {} // on any failure, fall back to the screen DC
 6935  
 6936                      bool captured = hdcSource != hdcScreen;
 6937  
 6938                      // paint the initial bitmap
 6939                      BitBlt( hdcScreenCompat, 0, 0, bmp.bmWidth, bmp.bmHeight, hdcSource,
 6940                          captured ? 0 : monInfo.rcMonitor.left, captured ? 0 : monInfo.rcMonitor.top, SRCCOPY|CAPTUREBLT );
 6941                      BitBlt( hdcScreenSaveCompat, 0, 0, bmp.bmWidth, bmp.bmHeight, hdcSource,
 6942                          captured ? 0 : monInfo.rcMonitor.left, captured ? 0 : monInfo.rcMonitor.top, SRCCOPY|CAPTUREBLT );
 6943  
 6944                      if( captured )
 6945                      {
 6946                          OutputDebug(L"Captured screen\n");
 6947                          auto bitmap = GetCurrentObject( hdcSource, OBJ_BITMAP );
 6948                          DeleteObject( bitmap );
 6949                          DeleteDC( hdcSource );
 6950                      }
 6951  
 6952                      // Create drawing pen
 6953                      hDrawingPen = CreatePen(PS_SOLID, g_PenWidth, g_PenColor & 0xFFFFFF);
 6954  
 6955                      g_BlankedScreen = FALSE;
 6956                      g_HaveTyped = FALSE;
 6957                      g_Drawing = FALSE;
 6958                      g_TypeMode = TypeModeOff;
 6959                      g_HaveDrawn = FALSE;
 6960                      EnableDisableStickyKeys( TRUE );
 6961  
 6962                      // Go full screen
 6963                      g_ActiveWindow = GetForegroundWindow();
 6964                      OutputDebug( L"active window: %x\n", PtrToLong(g_ActiveWindow) );
 6965  
 6966                      if( lParam != LIVE_DRAW_ZOOM) {
 6967  
 6968                          OutputDebug(L"Calling ShowMainWindow\n");
 6969                          ShowMainWindow(hWnd, monInfo, width, height);
 6970                      }
 6971  
 6972                      // Start telescoping zoom. Lparam is non-zero if this
 6973                      // was a real hotkey and not the message we send ourself to enter
 6974                      // unzoomed drawing mode.
 6975  
 6976                      //
 6977                      // Are we switching from live zoom to standard zoom?
 6978                      //
 6979  #if WINDOWS_CURSOR_RECORDING_WORKAROUND
 6980                      if( IsWindowVisible( g_hWndLiveZoom ) && !g_LiveZoomLevelOne ) {
 6981  #else
 6982                      if( IsWindowVisible( g_hWndLiveZoom )) {
 6983  #endif
 6984  
 6985                          // Enter drawing mode
 6986                          OutputDebug(L"Enter liveZoom draw\n");
 6987                          g_LiveZoomSourceRect = *reinterpret_cast<RECT *>(SendMessage( g_hWndLiveZoom, WM_USER_GET_SOURCE_RECT, 0, 0 ));
 6988                          g_LiveZoomLevel = *reinterpret_cast<float*>(SendMessage(g_hWndLiveZoom, WM_USER_GET_ZOOM_LEVEL, 0, 0));
 6989  
 6990                          // Set live zoom level to 1 in preparation of us being full screen static
 6991                          zoomLevel = 1.0;
 6992                          zoomTelescopeTarget = 1.0;
 6993                          if (lParam != LIVE_DRAW_ZOOM) {
 6994  
 6995                              g_ZoomOnLiveZoom = TRUE;
 6996                          }
 6997  
 6998                          UpdateWindow( hWnd ); // overwrites where cursor erased
 6999                          if( lParam != SHALLOW_ZOOM )
 7000                          {
 7001                              // Put the drawing cursor where the magnified cursor was
 7002                              OutputDebug(L"Setting cursor\n");
 7003  
 7004                              if (lParam != LIVE_DRAW_ZOOM)
 7005                              {
 7006                                  cursorPos = ScalePointInRects( cursorPos, g_LiveZoomSourceRect, monInfo.rcMonitor );
 7007                                  SetCursorPos( cursorPos.x, cursorPos.y );
 7008                                  UpdateWindow( hWnd ); // overwrites where cursor erased
 7009                                  SendMessage( hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM( cursorPos.x, cursorPos.y ));
 7010                              }
 7011                          }
 7012                          else
 7013                          {
 7014                              InvalidateRect( hWnd, NULL, FALSE );
 7015                          }
 7016                          UpdateWindow( hWnd );
 7017                          if( g_RecordToggle )
 7018                          {
 7019                              g_SelectRectangle.UpdateOwner( hWnd );
 7020                          }
 7021                          if( lParam != LIVE_DRAW_ZOOM ) {
 7022  
 7023                              OutputDebug(L"Calling ShowMainWindow 2\n");
 7024  
 7025                              ShowWindow( g_hWndLiveZoom, SW_HIDE );
 7026                          }
 7027  
 7028                      } else if( lParam != 0 && lParam != LIVE_DRAW_ZOOM ) {
 7029  
 7030                          zoomTelescopeStep = ZOOM_LEVEL_STEP_IN;
 7031                          zoomTelescopeTarget = g_ZoomLevels[g_SliderZoomLevel];
 7032                          if( g_AnimateZoom )
 7033                          {
 7034                              zoomLevel = static_cast<float>(1.0) * zoomTelescopeStep;
 7035                              g_TelescopingZoomLastTick = GetTickCount64();
 7036                          }
 7037                          else
 7038                              zoomLevel = zoomTelescopeTarget;
 7039                          SetTimer( hWnd, 1, ZOOM_LEVEL_STEP_TIME, NULL );
 7040                      }
 7041  
 7042                  } else {
 7043  
 7044                      OutputDebug( L"Zoom off: don't animate=%d\n", lParam );
 7045                      // turn off liveDraw
 7046                      SetLayeredWindowAttributes(hWnd, 0, 255, LWA_ALPHA);
 7047  
 7048                      if( lParam != SHALLOW_DESTROY && !g_ZoomOnLiveZoom && g_AnimateZoom &&
 7049                          g_TelescopeZoomOut && zoomTelescopeTarget != 1 ) {
 7050  
 7051                          // Start telescoping zoom.
 7052                          zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT;
 7053                          zoomTelescopeTarget = 1.0;
 7054                          g_TelescopingZoomLastTick = GetTickCount64();
 7055                          SetTimer( hWnd, 2, ZOOM_LEVEL_STEP_TIME, NULL );
 7056  
 7057                      } else {
 7058  
 7059                          // Simulate timer expiration
 7060                          zoomTelescopeStep = 0;
 7061                          zoomTelescopeTarget = zoomLevel = 1.0;
 7062                          SendMessage( hWnd, WM_TIMER, 2, lParam );
 7063                      }
 7064                  }
 7065              }
 7066              break;
 7067          }
 7068          return TRUE;
 7069  
 7070      case WM_POINTERUPDATE: {
 7071          penInverted = IsPenInverted(wParam);
 7072          OutputDebug( L"WM_POINTERUPDATE: contact: %d button down: %d X: %d Y: %d\n",
 7073              IS_POINTER_INCONTACT_WPARAM(wParam),
 7074              penInverted,
 7075              GET_X_LPARAM(lParam),
 7076              GET_Y_LPARAM(lParam));
 7077          if( penInverted != g_PenInverted) {
 7078  
 7079              g_PenInverted = penInverted;
 7080              if (g_PenInverted) {
 7081                  if (PopDrawUndo(hdcScreenCompat, &drawUndoList, width, height)) {
 7082  
 7083                      SaveCursorArea(hdcScreenCursorCompat, hdcScreenCompat, prevPt);
 7084                      InvalidateRect(hWnd, NULL, FALSE);
 7085                  }
 7086              }
 7087          } else if( g_PenDown && !penInverted) {
 7088  
 7089              SendPenMessage(hWnd, WM_MOUSEMOVE, lParam);
 7090          }
 7091          }
 7092          return TRUE;
 7093  
 7094      case WM_POINTERUP:
 7095          OutputDebug(L"WM_POINTERUP\n");
 7096          penInverted = IsPenInverted(wParam);
 7097          if (!penInverted) {
 7098  
 7099              SendPenMessage(hWnd, WM_LBUTTONUP, lParam);
 7100              SendPenMessage(hWnd, WM_RBUTTONDOWN, lParam);
 7101              g_PenDown = FALSE;
 7102          }
 7103          break;
 7104  
 7105      case WM_POINTERDOWN:
 7106          OutputDebug(L"WM_POINTERDOWN\n");
 7107          penInverted = IsPenInverted(wParam);
 7108          if (!penInverted) {
 7109  
 7110              g_PenDown = TRUE;
 7111  
 7112              // Enter drawing mode
 7113              SendPenMessage(hWnd, WM_LBUTTONDOWN, lParam);
 7114              SendPenMessage(hWnd, WM_MOUSEMOVE, lParam);
 7115              SendPenMessage(hWnd, WM_LBUTTONUP, lParam);
 7116              SendPenMessage(hWnd, WM_MOUSEMOVE, lParam);
 7117              PopDrawUndo(hdcScreenCompat, &drawUndoList, width, height);
 7118  
 7119              // Enter tracing mode
 7120              SendPenMessage(hWnd, WM_LBUTTONDOWN, lParam);
 7121          }
 7122          break;
 7123  
 7124      case WM_KILLFOCUS:
 7125          if( ( g_RecordCropping == FALSE ) && g_Zoomed && !g_bSaveInProgress ) {
 7126  
 7127              // Turn off zoom if not in liveDraw
 7128              DWORD layeringFlag;
 7129              GetLayeredWindowAttributes(hWnd, NULL, NULL, &layeringFlag);
 7130              if( !(layeringFlag & LWA_COLORKEY)) {
 7131  
 7132                  PostMessage(hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0);
 7133              }
 7134          }
 7135          break;
 7136  
 7137      case WM_MOUSEWHEEL:
 7138  
 7139          //
 7140          // Zoom or modify break timer
 7141          //
 7142          if( GET_WHEEL_DELTA_WPARAM(wParam) < 0 )
 7143              wParam -= (WHEEL_DELTA-1) << 16;
 7144          else
 7145              wParam += (WHEEL_DELTA-1) << 16;
 7146          delta = GET_WHEEL_DELTA_WPARAM(wParam)/WHEEL_DELTA;
 7147          OutputDebug( L"mousewheel: wParam: %d delta: %d\n",
 7148                  GET_WHEEL_DELTA_WPARAM(wParam), delta );
 7149          if( g_Zoomed ) {
 7150  
 7151              if( g_TypeMode == TypeModeOff ) {
 7152  
 7153                  if( g_Drawing && (LOWORD( wParam ) & MK_CONTROL) ) {
 7154  
 7155                      ResizePen( hWnd, hdcScreenCompat, hdcScreenCursorCompat, prevPt,
 7156                          g_Tracing, &g_Drawing, g_LiveZoomLevel, TRUE, g_PenWidth + delta );
 7157  
 7158                  // Perform static zoom unless in liveDraw
 7159                  } else if( !( GetWindowLongPtr( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) ) {
 7160  
 7161                      if( delta > 0 ) zoomIn = TRUE;
 7162                      else {
 7163                          zoomIn = FALSE;
 7164                          delta = -delta;
 7165                      }
 7166                      while( delta-- ) {
 7167  
 7168                          if( zoomIn ) {
 7169  
 7170                              if( zoomTelescopeTarget < ZOOM_LEVEL_MAX ) {
 7171  
 7172                                  if( zoomTelescopeTarget < 2 ) {
 7173  
 7174                                      zoomTelescopeTarget = 2;
 7175  
 7176                                  } else {
 7177  
 7178                                      // Start telescoping zoom
 7179                                      zoomTelescopeTarget = zoomTelescopeTarget * 2;
 7180                                  }
 7181                                  zoomTelescopeStep = ZOOM_LEVEL_STEP_IN;
 7182                                  if( g_AnimateZoom )
 7183                                      zoomLevel *= zoomTelescopeStep;
 7184                                  else
 7185                                      zoomLevel = zoomTelescopeTarget;
 7186  
 7187                                  if( zoomLevel > zoomTelescopeTarget )
 7188                                      zoomLevel = zoomTelescopeTarget;
 7189                                  else
 7190                                      SetTimer( hWnd, 1, ZOOM_LEVEL_STEP_TIME, NULL );
 7191                              }
 7192  
 7193                          } else if( zoomTelescopeTarget > ZOOM_LEVEL_MIN ) {
 7194  
 7195                              // Let them more gradually zoom out from 2x to 1x
 7196                              if( zoomTelescopeTarget <= 2 ) {
 7197  
 7198                                  zoomTelescopeTarget *= .75;
 7199                                  if( zoomTelescopeTarget < ZOOM_LEVEL_MIN )
 7200                                      zoomTelescopeTarget = ZOOM_LEVEL_MIN;
 7201  
 7202                              } else {
 7203  
 7204                                  zoomTelescopeTarget = zoomTelescopeTarget/2;
 7205                              }
 7206                              zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT;
 7207                              if( g_AnimateZoom )
 7208                                  zoomLevel *= zoomTelescopeStep;
 7209                              else
 7210                                  zoomLevel = zoomTelescopeTarget;
 7211  
 7212                              if( zoomLevel < zoomTelescopeTarget )
 7213                              {
 7214                                  zoomLevel = zoomTelescopeTarget;
 7215                                  // Force update on final step out
 7216                                  InvalidateRect( hWnd, NULL, FALSE );
 7217                              }
 7218                              else
 7219                              {
 7220                                  SetTimer( hWnd, 1, ZOOM_LEVEL_STEP_TIME, NULL );
 7221                              }
 7222                          }
 7223                      }
 7224                      if( zoomLevel != zoomTelescopeTarget ) {
 7225  
 7226                          if( g_Drawing ) {
 7227  
 7228                              if( !g_Tracing ) {
 7229  
 7230                                  RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
 7231                              }
 7232                              //SetCursorPos( monInfo.rcMonitor.left + cursorPos.x,
 7233                              //		monInfo.rcMonitor.top + cursorPos.y );
 7234                          }
 7235                          InvalidateRect( hWnd, NULL, FALSE );
 7236                      }
 7237                  }
 7238              } else {
 7239  
 7240                  // Resize the text font
 7241                  if( (delta > 0 && g_FontScale > -20) || (delta < 0 && g_FontScale < 50 )) {
 7242  
 7243                      ClearTypingCursor(hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen);
 7244  
 7245                      g_FontScale -= delta;
 7246                      if( g_FontScale == 0 ) g_FontScale = 1;
 7247                      // Set lParam to 0 as part of message to keyup hander
 7248                      DeleteObject(hTypingFont);
 7249                      g_LogFont.lfHeight = max((int)(height / zoomLevel) / g_FontScale, 12);
 7250                      if (g_LogFont.lfHeight < 20)
 7251                          g_LogFont.lfQuality = NONANTIALIASED_QUALITY;
 7252                      else
 7253                          g_LogFont.lfQuality = ANTIALIASED_QUALITY;
 7254                      hTypingFont = CreateFontIndirect(&g_LogFont);
 7255                      SelectObject(hdcScreenCompat, hTypingFont);
 7256  
 7257                      DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc );
 7258                  }
 7259              }
 7260          } else if( g_TimerActive && (breakTimeout > 0 || delta )) {
 7261  
 7262              if( delta ) {
 7263  
 7264                  if( breakTimeout < 0 ) breakTimeout = 0;
 7265                  if( breakTimeout % 60 ) {
 7266                      breakTimeout += (60 - breakTimeout % 60);
 7267                      delta--;
 7268                  }
 7269                  breakTimeout += delta * 60;
 7270  
 7271              } else {
 7272  
 7273                  if( breakTimeout % 60 ) {
 7274                      breakTimeout -= breakTimeout % 60;
 7275                      delta--;
 7276                  }
 7277                  breakTimeout -= delta * 60;
 7278              }
 7279              if( breakTimeout < 0 ) breakTimeout = 0;
 7280              KillTimer( hWnd, 0 );
 7281              SetTimer( hWnd, 0, 1000, NULL );
 7282              InvalidateRect( hWnd, NULL, TRUE );
 7283          }
 7284  
 7285          if( zoomLevel != 1 && g_Drawing ) {
 7286  
 7287              // Constrain the mouse to the visible region
 7288              boundRc = BoundMouse( zoomTelescopeTarget, &monInfo, width, height, &cursorPos );
 7289  
 7290          } else {
 7291  
 7292              ClipCursor( NULL );
 7293          }
 7294          return TRUE;
 7295  
 7296      case WM_IME_CHAR:
 7297      case WM_CHAR:
 7298  
 7299          if( (g_TypeMode != TypeModeOff) && iswprint(static_cast<TCHAR>(wParam)) || (static_cast<TCHAR>(wParam) == L'&')) {
 7300              g_HaveTyped = TRUE;
 7301  
 7302              TCHAR	 vKey = static_cast<TCHAR>(wParam);
 7303  
 7304              g_HaveDrawn = TRUE;
 7305  
 7306              // Clear typing cursor
 7307              rc.left = textPt.x;
 7308              rc.top = textPt.y;
 7309              ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen );
 7310              if (g_TypeMode == TypeModeRightJustify) {
 7311  
 7312                  if( !g_TextBuffer.empty() || !g_TextBufferPreviousLines.empty() ) {
 7313  
 7314                      PopDrawUndo(hdcScreenCompat, &drawUndoList, width, height); //***
 7315                  }
 7316                  PushDrawUndo(hdcScreenCompat, &drawUndoList, width, height);
 7317  
 7318                  // Restore previous lines.
 7319                  wParam = 'X';
 7320                  DrawText(hdcScreenCompat, reinterpret_cast<PTCHAR>(&wParam), 1, &rc, DT_CALCRECT);
 7321                  const auto lineHeight = rc.bottom - rc.top;
 7322  
 7323                  rc.top -= static_cast< LONG >( g_TextBufferPreviousLines.size() ) * lineHeight;
 7324  
 7325                  // Draw the current character on the current line.
 7326                  g_TextBuffer += vKey;
 7327                  drawAllRightJustifiedLines( lineHeight );
 7328              }
 7329              else {
 7330                  DrawText( hdcScreenCompat, &vKey, 1, &rc, DT_CALCRECT|DT_NOPREFIX);
 7331                  DrawText( hdcScreenCompat, &vKey, 1, &rc, DT_LEFT|DT_NOPREFIX);
 7332                  textPt.x += rc.right - rc.left;
 7333              }
 7334              InvalidateRect( hWnd, NULL, TRUE );
 7335  
 7336              // Save the key for undo
 7337              P_TYPED_KEY newKey = static_cast<P_TYPED_KEY>(malloc( sizeof(TYPED_KEY) ));
 7338              newKey->rc = rc;
 7339              newKey->Next = typedKeyList;
 7340              typedKeyList = newKey;
 7341  
 7342              // Draw the typing cursor
 7343              DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc );
 7344              return FALSE;
 7345          }
 7346          break;
 7347  
 7348      case WM_KEYUP:
 7349          if( wParam == 'T' && (g_TypeMode == TypeModeOff)) {
 7350  
 7351              // lParam is 0 when we're resizing the font and so don't have a cursor that
 7352              // we need to restore
 7353              if( !g_Drawing && lParam == 0 ) {
 7354  
 7355                  OutputDebug(L"Entering typing mode and resetting cursor position\n");
 7356                  SendMessage( hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM( cursorPos.x, cursorPos.y));
 7357              }
 7358  
 7359              // Do they want to right-justify text?
 7360              OutputDebug(L"Keyup Shift: %x\n", GetAsyncKeyState(VK_SHIFT));
 7361              if(GetAsyncKeyState(VK_SHIFT) != 0 ) {
 7362  
 7363                  g_TypeMode = TypeModeRightJustify;
 7364                  g_TextBuffer.clear();
 7365  
 7366                  // Also empty all previous lines
 7367                  g_TextBufferPreviousLines = {};
 7368              }
 7369              else {
 7370  
 7371                  g_TypeMode = TypeModeLeftJustify;
 7372              }
 7373              textStartPt = cursorPos;
 7374              textPt = prevPt;
 7375  
 7376              g_HaveTyped = FALSE;
 7377  
 7378              // Get a font of a decent size
 7379              DeleteObject( hTypingFont );
 7380              g_LogFont.lfHeight = max( (int) (height / zoomLevel)/g_FontScale, 12 );
 7381              if (g_LogFont.lfHeight < 20)
 7382                  g_LogFont.lfQuality = NONANTIALIASED_QUALITY;
 7383              else
 7384                  g_LogFont.lfQuality = ANTIALIASED_QUALITY;
 7385              hTypingFont = CreateFontIndirect( &g_LogFont );
 7386              SelectObject( hdcScreenCompat, hTypingFont );
 7387  
 7388              // If lparam == 0 that means that we sent the message as part of a font resize
 7389              if( g_Drawing && lParam != 0) {
 7390  
 7391                  RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
 7392                  PushDrawUndo( hdcScreenCompat, &drawUndoList, width, height );
 7393  
 7394              } else if( !g_Drawing ) {
 7395  
 7396                  textPt = cursorPos;
 7397              }
 7398  
 7399              // Draw the typing cursor
 7400              DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc, true );
 7401              prevPt = textPt;
 7402          }
 7403          break;
 7404  
 7405      case WM_KEYDOWN:
 7406  
 7407          if( (g_TypeMode != TypeModeOff) && g_HaveTyped && static_cast<char>(wParam) != VK_UP && static_cast<char>(wParam) != VK_DOWN &&
 7408              (isprint( static_cast<char>(wParam)) ||
 7409              wParam == VK_RETURN || wParam == VK_DELETE || wParam == VK_BACK )) {
 7410  
 7411              if( wParam == VK_RETURN ) {
 7412  
 7413                  // Clear the typing cursor
 7414                  ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen );
 7415  
 7416                  if( g_TypeMode == TypeModeRightJustify )
 7417                  {
 7418                      g_TextBufferPreviousLines.push_back( g_TextBuffer );
 7419                      g_TextBuffer.clear();
 7420                  }
 7421                  else
 7422                  {
 7423                      // Insert a fake return key in the list to undo.
 7424                      P_TYPED_KEY newKey = static_cast<P_TYPED_KEY>(malloc(sizeof(TYPED_KEY)));
 7425                      newKey->rc.left = textPt.x;
 7426                      newKey->rc.top = textPt.y;
 7427                      newKey->rc.right = newKey->rc.left;
 7428                      newKey->rc.bottom = newKey->rc.top;
 7429                      newKey->Next = typedKeyList;
 7430                      typedKeyList = newKey;
 7431                  }
 7432  
 7433                  wParam = 'X';
 7434                  DrawText( hdcScreenCompat, reinterpret_cast<PTCHAR>(&wParam), 1, &rc, DT_CALCRECT );
 7435                  textPt.x = prevPt.x; // + g_PenWidth;
 7436                  textPt.y += rc.bottom - rc.top;
 7437  
 7438                  // Draw the typing cursor
 7439                  DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc );
 7440              } else if( wParam == VK_DELETE || wParam == VK_BACK ) {
 7441  
 7442                  P_TYPED_KEY	deletedKey = typedKeyList;
 7443                  if( deletedKey ) {
 7444  
 7445                      // Clear the typing cursor
 7446                      ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen );
 7447  
 7448                      if( g_TypeMode == TypeModeRightJustify ) {
 7449  
 7450                          if( !g_TextBuffer.empty() || !g_TextBufferPreviousLines.empty() ) {
 7451  
 7452                              PopDrawUndo( hdcScreenCompat, &drawUndoList, width, height );
 7453                          }
 7454                          PushDrawUndo( hdcScreenCompat, &drawUndoList, width, height );
 7455  
 7456                          rc.left = textPt.x;
 7457                          rc.top = textPt.y;
 7458  
 7459                          // Restore the previous lines.
 7460                          wParam = 'X';
 7461                          DrawText( hdcScreenCompat, reinterpret_cast<PTCHAR>(&wParam), 1, &rc, DT_CALCRECT );
 7462                          const auto lineHeight = rc.bottom - rc.top;
 7463  
 7464                          const bool lineWasEmpty = g_TextBuffer.empty();
 7465                          drawAllRightJustifiedLines( lineHeight, true );
 7466                          if( lineWasEmpty && !g_TextBufferPreviousLines.empty() )
 7467                          {
 7468                              g_TextBuffer = g_TextBufferPreviousLines.back();
 7469                              g_TextBufferPreviousLines.pop_back();
 7470                              textPt.y -= lineHeight;
 7471                          }
 7472                      }
 7473                      else {
 7474                          RECT rect = deletedKey->rc;
 7475                          if (g_BlankedScreen) {
 7476  
 7477                              BlankScreenArea(hdcScreenCompat, &rect, g_BlankedScreen);
 7478                          }
 7479                          else {
 7480  
 7481                              BitBlt(hdcScreenCompat, rect.left, rect.top, rect.right - rect.left,
 7482                                  rect.bottom - rect.top, hdcScreenSaveCompat, rect.left, rect.top, SRCCOPY | CAPTUREBLT );
 7483                          }
 7484                          InvalidateRect( hWnd, NULL, FALSE );
 7485  
 7486                          textPt.x = rect.left;
 7487                          textPt.y = rect.top;
 7488  
 7489                          typedKeyList = deletedKey->Next;
 7490                          free(deletedKey);
 7491  
 7492                          // Refresh cursor if we deleted the last key
 7493                          if( typedKeyList == NULL ) {
 7494  
 7495                              SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ) );
 7496                          }
 7497                      }
 7498                      DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc );
 7499                  }
 7500              }
 7501              break;
 7502          }
 7503          switch (wParam) {
 7504          case 'R':
 7505          case 'B':
 7506          case 'Y':
 7507          case 'O':
 7508          case 'G':
 7509          case 'X':
 7510          case 'P':
 7511              if( (g_Zoomed || g_TimerActive) && (g_TypeMode == TypeModeOff)) {
 7512  
 7513                  PDWORD	penColor;
 7514                  if( g_TimerActive )
 7515                      penColor = &g_BreakPenColor;
 7516                  else
 7517                      penColor = &g_PenColor;
 7518  
 7519                  if( wParam == 'R' )		 *penColor = COLOR_RED;
 7520                  else if( wParam == 'G' ) *penColor = COLOR_GREEN;
 7521                  else if( wParam == 'B' ) *penColor = COLOR_BLUE;
 7522                  else if( wParam == 'Y' ) *penColor = COLOR_YELLOW;
 7523                  else if( wParam == 'O' ) *penColor = COLOR_ORANGE;
 7524                  else if( wParam == 'P' ) *penColor = COLOR_PINK;
 7525                  else if( wParam == 'X' )
 7526                  {
 7527                      if( GetWindowLong( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED )
 7528                      {
 7529                          // Blur is not supported in LiveDraw
 7530                          break;
 7531                      }
 7532                      *penColor = COLOR_BLUR;
 7533                  }
 7534  
 7535                  bool shift = GetKeyState( VK_SHIFT ) & 0x8000;
 7536                  if( shift && ( GetWindowLong( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) )
 7537                  {
 7538                      // Highlight is not supported in LiveDraw
 7539                      break;
 7540                  }
 7541  
 7542                  reg.WriteRegSettings( RegSettings );
 7543                  DeleteObject( hDrawingPen );
 7544                  SetTextColor( hdcScreenCompat, *penColor );
 7545  
 7546                  // Highlight and blur level
 7547                  if( shift && *penColor != COLOR_BLUR )
 7548                  {
 7549                      *penColor |= (g_AlphaBlend << 24);
 7550                  }
 7551                  else
 7552                  {
 7553                      if( *penColor == COLOR_BLUR )
 7554                      {
 7555                          g_BlurRadius = shift ? STRONG_BLUR_RADIUS : NORMAL_BLUR_RADIUS;
 7556                      }
 7557                      *penColor |= (0xFF << 24);
 7558                  }
 7559                  hDrawingPen = CreatePen(PS_SOLID, g_PenWidth, *penColor & 0xFFFFFF);
 7560  
 7561                  SelectObject( hdcScreenCompat, hDrawingPen );
 7562                  if( g_Drawing ) {
 7563  
 7564                      SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
 7565  
 7566                  } else if( g_TimerActive ) {
 7567  
 7568                      InvalidateRect( hWnd, NULL, FALSE );
 7569  
 7570                  } else if( g_TypeMode != TypeModeOff ) {
 7571  
 7572                      ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen );
 7573                      DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc );
 7574                      InvalidateRect( hWnd, NULL, FALSE );
 7575                  }
 7576              }
 7577              break;
 7578  
 7579          case 'Z':
 7580              if( (GetKeyState( VK_CONTROL ) & 0x8000 ) && g_HaveDrawn && !g_Tracing ) {
 7581  
 7582                  if( PopDrawUndo( hdcScreenCompat, &drawUndoList, width, height )) {
 7583  
 7584                      if( g_Drawing ) {
 7585  
 7586                          SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
 7587                          SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
 7588                      }
 7589                      else {
 7590  
 7591                          SaveCursorArea(hdcScreenCursorCompat, hdcScreenCompat, prevPt);
 7592                      }
 7593                      InvalidateRect( hWnd, NULL, FALSE );
 7594                  }
 7595              }
 7596              break;
 7597  
 7598          case VK_SPACE:
 7599              if( g_Drawing && !g_Tracing ) {
 7600  
 7601                  SetCursorPos(  boundRc.left + (boundRc.right - boundRc.left)/2,
 7602                           boundRc.top + (boundRc.bottom - boundRc.top)/2 );
 7603                  SendMessage( hWnd, WM_MOUSEMOVE, 0,
 7604                      MAKELPARAM( (boundRc.right - boundRc.left)/2,
 7605                                  (boundRc.bottom - boundRc.top)/2 ));
 7606              }
 7607              break;
 7608  
 7609          case 'W':
 7610          case 'K':
 7611              // Block user-driven sketch pad in liveDraw
 7612              if( lParam != LIVE_DRAW_ZOOM
 7613                  && ( GetWindowLongPtr( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) )
 7614              {
 7615                  break;
 7616              }
 7617  
 7618              // Don't allow screen blanking while we've got the typing cursor active
 7619              // because we don't really handle going from white to black.
 7620              if( g_Zoomed && (g_TypeMode == TypeModeOff)) {
 7621  
 7622                  if( !g_Drawing ) {
 7623  
 7624                      SendMessage( hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM( cursorPos.x, cursorPos.y));
 7625                  }
 7626                  // Restore area where cursor was previously
 7627                  RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
 7628                  PushDrawUndo( hdcScreenCompat, &drawUndoList, width, height );
 7629                  g_BlankedScreen = static_cast<int>(wParam);
 7630                  rc.top = rc.left = 0;
 7631                  rc.bottom = height;
 7632                  rc.right = width;
 7633                  BlankScreenArea( hdcScreenCompat, &rc, g_BlankedScreen );
 7634                  InvalidateRect( hWnd, NULL, FALSE );
 7635  
 7636                  // Save area that's going to be occupied by new cursor position
 7637                  SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
 7638                  SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
 7639              }
 7640              break;
 7641  
 7642          case 'E':
 7643              // Don't allow erase while we have the typing cursor active
 7644              if( g_HaveDrawn && (g_TypeMode == TypeModeOff)) {
 7645  
 7646                  DeleteDrawUndoList( &drawUndoList );
 7647                  g_HaveDrawn = FALSE;
 7648                  OutputDebug(L"Erase\n");
 7649                  if(GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_LAYERED) {
 7650                      SendMessage(hWnd, WM_KEYDOWN, 'K', 0);
 7651                  }
 7652                  else {
 7653                      BitBlt(hdcScreenCompat, 0, 0, bmp.bmWidth,
 7654                          bmp.bmHeight, hdcScreenSaveCompat, 0, 0, SRCCOPY | CAPTUREBLT);
 7655  
 7656                      if (g_Drawing) {
 7657  
 7658                          OutputDebug(L"Erase: draw cursor\n");
 7659                          SaveCursorArea(hdcScreenCursorCompat, hdcScreenCompat, prevPt);
 7660                          DrawCursor(hdcScreenCompat, prevPt, zoomLevel, width, height);
 7661                          g_HaveDrawn = TRUE;
 7662                      }
 7663                  }
 7664                  InvalidateRect( hWnd, NULL, FALSE );
 7665                  g_BlankedScreen = FALSE;
 7666              }
 7667              break;
 7668  
 7669          case VK_UP:
 7670              SendMessage( hWnd, WM_MOUSEWHEEL,
 7671                  MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 || GetAsyncKeyState( VK_RCONTROL ) != 0 ?
 7672                          MK_CONTROL: 0, WHEEL_DELTA), 0 );
 7673              return TRUE;
 7674  
 7675          case VK_DOWN:
 7676              SendMessage( hWnd, WM_MOUSEWHEEL,
 7677                  MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 || GetAsyncKeyState( VK_RCONTROL ) != 0 ?
 7678                          MK_CONTROL: 0, -WHEEL_DELTA), 0 );
 7679              return TRUE;
 7680  
 7681          case VK_LEFT:
 7682          case VK_RIGHT:
 7683              if( wParam == VK_RIGHT ) delta = 10;
 7684              else					  delta = -10;
 7685              if( g_TimerActive && (breakTimeout > 0 || delta )) {
 7686  
 7687                  if( breakTimeout < 0 ) breakTimeout = 0;
 7688                  breakTimeout += delta;
 7689                  breakTimeout -= (breakTimeout % 10);
 7690                  if( breakTimeout < 0 ) breakTimeout = 0;
 7691                  KillTimer( hWnd, 0 );
 7692                  SetTimer( hWnd, 0, 1000, NULL );
 7693                  InvalidateRect( hWnd, NULL, TRUE );
 7694              }
 7695              break;
 7696  
 7697          case VK_ESCAPE:
 7698              if( g_TypeMode != TypeModeOff) {
 7699  
 7700                  // Turn off
 7701                  SendMessage( hWnd, WM_USER_TYPING_OFF, 0, 0 );
 7702  
 7703              } else {
 7704  
 7705                  forcePenResize = TRUE;
 7706                  PostMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 );
 7707  
 7708                  // In case we were in liveDraw
 7709                  if( GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_LAYERED) {
 7710  
 7711                      KillTimer(hWnd, 3);
 7712                      LONG_PTR exStyle = GetWindowLongPtr(hWnd, GWL_EXSTYLE);
 7713                      SetWindowLongPtr(hWnd, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
 7714                      pMagSetWindowFilterList( g_hWndLiveZoomMag, MW_FILTERMODE_EXCLUDE, 1, &hWnd );
 7715                      SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, TRUE, 0 );
 7716                  }
 7717              }
 7718              break;
 7719          }
 7720          return TRUE;
 7721  
 7722      case WM_RBUTTONDOWN:
 7723          SendMessage( hWnd, WM_USER_EXIT_MODE, 0, 0 );
 7724          break;
 7725  
 7726      case WM_MOUSEMOVE:
 7727          OutputDebug(L"MOUSEMOVE: zoomed: %d drawing: %d tracing: %d\n",
 7728              g_Zoomed, g_Drawing, g_Tracing);
 7729  
 7730          OutputDebug(L"Window visible: %d Topmost: %d\n", IsWindowVisible(hWnd), GetWindowLong(hWnd, GWL_EXSTYLE)& WS_EX_TOPMOST);
 7731          if( g_Zoomed && g_TelescopingZoomLastTick != 0ull && !g_Drawing && !g_Tracing )
 7732          {
 7733              ULONG64 now = GetTickCount64();
 7734              if( now - g_TelescopingZoomLastTick >= ZOOM_LEVEL_STEP_TIME )
 7735              {
 7736                  doTelescopingZoomTimer( false );
 7737              }
 7738          }
 7739  
 7740          if( g_Zoomed && (g_TypeMode == TypeModeOff) && !g_bSaveInProgress ) {
 7741  
 7742              if( g_Drawing ) {
 7743  
 7744                  OutputDebug(L"Mousemove: Drawing\n");
 7745  
 7746                  POINT currentPt;
 7747  
 7748                  // Are we in pen mode on a tablet?
 7749                  lParam = ScalePenPosition( zoomLevel, &monInfo, boundRc, message, lParam);
 7750                  currentPt.x = LOWORD(lParam);
 7751                  currentPt.y = HIWORD(lParam);
 7752  
 7753                  if(lParam == 0) {
 7754  
 7755                      // Drop it
 7756                      OutputDebug(L"Mousemove: Dropping\n");
 7757                      break;
 7758  
 7759                  } else if(g_DrawingShape) {
 7760  
 7761                      SetROP2(hdcScreenCompat, R2_NOTXORPEN);
 7762  
 7763                      // If a previous target rectangle exists, erase
 7764                      // it by drawing another rectangle on top.
 7765                      if( g_rcRectangle.top != g_rcRectangle.bottom ||
 7766                          g_rcRectangle.left != g_rcRectangle.right )
 7767                      {
 7768                          if( prevPenWidth != g_PenWidth )
 7769                          {
 7770                              auto penWidth = g_PenWidth;
 7771                              g_PenWidth = prevPenWidth;
 7772  
 7773                              auto prevPen = CreatePen( PS_SOLID, g_PenWidth, g_PenColor & 0xFFFFFF );
 7774                              SelectObject( hdcScreenCompat, prevPen );
 7775  
 7776                              DrawShape( g_DrawingShape, hdcScreenCompat, &g_rcRectangle );
 7777  
 7778                              g_PenWidth = penWidth;
 7779                              SelectObject( hdcScreenCompat, hDrawingPen );
 7780                              DeleteObject( prevPen );
 7781                          }
 7782                          else
 7783                          {
 7784                              if (PEN_COLOR_HIGHLIGHT(g_PenColor))
 7785                              {
 7786                                  // copy original bitmap to screen bitmap to erase previous highlight
 7787                                  BitBlt(hdcScreenCompat, 0, 0, bmp.bmWidth, bmp.bmHeight, drawUndoList->hDc, 0, 0, SRCCOPY | CAPTUREBLT);
 7788                              }
 7789                              else
 7790                              {
 7791                                  DrawShape(g_DrawingShape, hdcScreenCompat, &g_rcRectangle, PEN_COLOR_HIGHLIGHT(g_PenColor));
 7792                              }
 7793                          }
 7794                      }
 7795  
 7796                      // Save the coordinates of the target rectangle.
 7797                      // Avoid invalid rectangles by ensuring that the
 7798                      // value of the left coordinate is greater than
 7799                      // that of the right, and that the value of the
 7800                      // bottom coordinate is greater than that of
 7801                      // the top.
 7802                      if( g_DrawingShape == DRAW_LINE ||
 7803                          g_DrawingShape == DRAW_ARROW ) {
 7804  
 7805                          g_rcRectangle.right = static_cast<LONG>(LOWORD(lParam));
 7806                          g_rcRectangle.bottom = static_cast<LONG>(HIWORD(lParam));
 7807  
 7808                      } else {
 7809  
 7810                          if ((g_RectangleAnchor.x < currentPt.x) &&
 7811                                  (g_RectangleAnchor.y > currentPt.y)) {
 7812  
 7813                              SetRect(&g_rcRectangle, g_RectangleAnchor.x, currentPt.y,
 7814                                  currentPt.x, g_RectangleAnchor.y);
 7815  
 7816                          } else if ((g_RectangleAnchor.x > currentPt.x) &&
 7817                                  (g_RectangleAnchor.y > currentPt.y )) {
 7818  
 7819                              SetRect(&g_rcRectangle, currentPt.x,
 7820                                  currentPt.y, g_RectangleAnchor.x,g_RectangleAnchor.y);
 7821  
 7822                          } else if ((g_RectangleAnchor.x > currentPt.x) &&
 7823                                  (g_RectangleAnchor.y < currentPt.y )) {
 7824  
 7825                              SetRect(&g_rcRectangle, currentPt.x, g_RectangleAnchor.y,
 7826                                  g_RectangleAnchor.x, currentPt.y );
 7827                          } else {
 7828  
 7829                              SetRect(&g_rcRectangle, g_RectangleAnchor.x, g_RectangleAnchor.y,
 7830                                  currentPt.x, currentPt.y );
 7831                          }
 7832                      }
 7833  
 7834                      if (g_rcRectangle.left != g_rcRectangle.right ||
 7835                          g_rcRectangle.top != g_rcRectangle.bottom) {
 7836  
 7837                          // Draw the new target rectangle.
 7838                          DrawShape(g_DrawingShape, hdcScreenCompat, &g_rcRectangle, PEN_COLOR_HIGHLIGHT(g_PenColor));
 7839                          OutputDebug(L"SHAPE: (%d, %d) - (%d, %d)\n", g_rcRectangle.left, g_rcRectangle.top,
 7840                              g_rcRectangle.right, g_rcRectangle.bottom);
 7841                      }
 7842  
 7843                      prevPenWidth = g_PenWidth;
 7844                      SetROP2( hdcScreenCompat, R2_NOP );
 7845                  }
 7846                  else if (g_Tracing) {
 7847  
 7848                      OutputDebug(L"Mousemove: Tracing\n");
 7849  
 7850                      g_HaveDrawn = TRUE;
 7851                      Gdiplus::Graphics	dstGraphics(hdcScreenCompat);
 7852                      if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
 7853                      {
 7854                          dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
 7855                      }
 7856                      Gdiplus::Color	color = ColorFromColorRef(g_PenColor);
 7857                      Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
 7858                      pen.SetLineCap(Gdiplus::LineCapRound, Gdiplus::LineCapRound, Gdiplus::DashCapRound);
 7859  
 7860                      // If highlighting, use a double layer approach
 7861                      OutputDebug(L"PenColor: %x\n", g_PenColor);
 7862                      OutputDebug(L"Blur color: %x\n", COLOR_BLUR);
 7863                      if( (g_PenColor & 0xFFFFFF) == COLOR_BLUR) {
 7864  
 7865                          OutputDebug(L"BLUR\n");
 7866  
 7867                          // Restore area where cursor was previously
 7868                          RestoreCursorArea(hdcScreenCompat, hdcScreenCursorCompat, prevPt);
 7869  
 7870                          // Create a new bitmap that's the size of the area covered by the line + 2 * g_PenWidth
 7871                          Gdiplus::Rect lineBounds = GetLineBounds( prevPt, currentPt, g_PenWidth );
 7872                          Gdiplus::Bitmap* lineBitmap = DrawBitmapLine(lineBounds, prevPt, currentPt, &pen);
 7873                          Gdiplus::BitmapData* lineData = LockGdiPlusBitmap(lineBitmap);
 7874                          BYTE* pPixels = static_cast<BYTE*>(lineData->Scan0);
 7875  
 7876                          // Create a GDI bitmap that's the size of the lineBounds rectangle
 7877                          Gdiplus::Bitmap *blurBitmap = CreateGdiplusBitmap( hdcScreenCompat, // oldestUndo->hDc,
 7878                                              lineBounds.X, lineBounds.Y, lineBounds.Width, lineBounds.Height);
 7879  
 7880                          // Blur it
 7881                          BitmapBlur(blurBitmap);
 7882                          BlurScreen(hdcScreenCompat, &lineBounds, blurBitmap, pPixels);
 7883  
 7884                          // Unlock the bits
 7885                          lineBitmap->UnlockBits(lineData);
 7886                          delete lineBitmap;
 7887                          delete blurBitmap;
 7888  
 7889                          // Invalidate the updated rectangle
 7890                          InvalidateGdiplusRect( hWnd, lineBounds );
 7891  
 7892                          // Save area that's going to be occupied by new cursor position
 7893                          SaveCursorArea(hdcScreenCursorCompat, hdcScreenCompat, currentPt);
 7894  
 7895                          // Draw new cursor
 7896                          DrawCursor(hdcScreenCompat, currentPt, zoomLevel, width, height);
 7897                      }
 7898                      else if(PEN_COLOR_HIGHLIGHT(g_PenColor)) {
 7899  
 7900                          OutputDebug(L"HIGHLIGHT\n");
 7901  
 7902                          // This is a highlighting pen color
 7903                          Gdiplus::Rect lineBounds = GetLineBounds(prevPt, currentPt, g_PenWidth);
 7904                          Gdiplus::Bitmap* lineBitmap = DrawBitmapLine(lineBounds, prevPt, currentPt, &pen);
 7905                          Gdiplus::BitmapData* lineData = LockGdiPlusBitmap(lineBitmap);
 7906                          BYTE* pPixels = static_cast<BYTE*>(lineData->Scan0);
 7907  
 7908                          // Create a DIB section for efficient pixel manipulation
 7909                          BITMAPINFO bmi = { 0 };
 7910                          bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
 7911                          bmi.bmiHeader.biWidth = lineBounds.Width;
 7912                          bmi.bmiHeader.biHeight = -lineBounds.Height;  // Top-down DIB
 7913                          bmi.bmiHeader.biPlanes = 1;
 7914                          bmi.bmiHeader.biBitCount = 32;  // 32 bits per pixel
 7915                          bmi.bmiHeader.biCompression = BI_RGB;
 7916  
 7917                          VOID* pDIBBits;
 7918                          HBITMAP hDIB = CreateDIBSection(hdcScreenCompat, &bmi, DIB_RGB_COLORS, &pDIBBits, NULL, 0);
 7919  
 7920                          HDC hdcDIB = CreateCompatibleDC(hdcScreenCompat);
 7921                          SelectObject(hdcDIB, hDIB);
 7922  
 7923                          // Copy the relevant part of hdcScreenCompat to the DIB
 7924                          BitBlt(hdcDIB, 0, 0, lineBounds.Width, lineBounds.Height, hdcScreenCompat, lineBounds.X, lineBounds.Y, SRCCOPY);
 7925  
 7926                          // Pointer to the DIB bits
 7927                          BYTE* pDestPixels = static_cast<BYTE*>(pDIBBits);
 7928  
 7929                          // Pointer to screen bits
 7930                          HDC hdcDIBOrig;
 7931                          HBITMAP hDibOrigBitmap, hDibBitmap;
 7932                          P_DRAW_UNDO oldestUndo = GetOldestUndo(drawUndoList);
 7933                          BYTE* pDestPixels2 = CreateBitmapMemoryDIB(hdcScreenCompat, oldestUndo->hDc, &lineBounds,
 7934                                                  &hdcDIBOrig, &hDibBitmap, &hDibOrigBitmap);
 7935  
 7936                          for (int local_y = 0; local_y < lineBounds.Height; ++local_y) {
 7937                              for (int local_x = 0; local_x < lineBounds.Width; ++local_x) {
 7938                                  int index = (local_y * lineBounds.Width * 4) + (local_x * 4); // Assuming 4 bytes per pixel
 7939                                  // BYTE b = pPixels[index + 0];  // Blue channel
 7940                                  // BYTE g = pPixels[index + 1];  // Green channel
 7941                                  // BYTE r = pPixels[index + 2];  // Red channel
 7942                                  BYTE a = pPixels[index + 3];  // Alpha channel
 7943  
 7944                                  // Check if this is a drawn pixel
 7945                                  if (a != 0) {
 7946                                      // Assuming pDestPixels is a valid pointer to the destination bitmap's pixel data
 7947                                      BYTE destB = pDestPixels2[index + 0];  // Blue channel
 7948                                      BYTE destG = pDestPixels2[index + 1];  // Green channel
 7949                                      BYTE destR = pDestPixels2[index + 2];  // Red channel
 7950  
 7951                                      // Create a COLORREF value from the destination pixel data
 7952                                      COLORREF currentPixel = RGB(destR, destG, destB);
 7953                                      // Blend the colors
 7954                                      COLORREF newPixel = BlendColors(currentPixel, g_PenColor);
 7955                                      // Update the destination pixel data with the new color
 7956                                      pDestPixels[index + 0] = GetBValue(newPixel);
 7957                                      pDestPixels[index + 1] = GetGValue(newPixel);
 7958                                      pDestPixels[index + 2] = GetRValue(newPixel);
 7959                                  }
 7960                              }
 7961                          }
 7962  
 7963                          // Copy the updated DIB back to hdcScreenCompat
 7964                          BitBlt(hdcScreenCompat, lineBounds.X, lineBounds.Y, lineBounds.Width, lineBounds.Height, hdcDIB, 0, 0, SRCCOPY);
 7965  
 7966                          // Clean up
 7967                          DeleteObject(hDIB);
 7968                          DeleteDC(hdcDIB);
 7969  
 7970                          SelectObject(hdcDIBOrig, hDibOrigBitmap);
 7971                          DeleteObject(hDibBitmap);
 7972                          DeleteDC(hdcDIBOrig);
 7973  
 7974                          // Invalidate the updated rectangle
 7975                          InvalidateGdiplusRect(hWnd, lineBounds);
 7976                      }
 7977                      else {
 7978  
 7979                          // Normal tracing
 7980                          dstGraphics.DrawLine(&pen, static_cast<INT>(prevPt.x), static_cast<INT>(prevPt.y),
 7981                                  static_cast<INT>(currentPt.x), static_cast<INT>(currentPt.y));
 7982                      }
 7983  
 7984                  } else {
 7985  
 7986                      OutputDebug(L"Mousemove: Moving cursor\n");
 7987  
 7988                      // Restore area where cursor was previously
 7989                      RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
 7990  
 7991                      // Save area that's going to be occupied by new cursor position
 7992                      SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, currentPt );
 7993  
 7994                      // Draw new cursor
 7995                      DrawCursor( hdcScreenCompat, currentPt, zoomLevel, width, height );
 7996                  }
 7997  
 7998                  if( g_DrawingShape ) {
 7999  
 8000                      InvalidateRect( hWnd, NULL, FALSE );
 8001  
 8002                  } else {
 8003  
 8004                      // Invalidate area just modified
 8005                      InvalidateCursorMoveArea( hWnd, zoomLevel, width, height, currentPt, prevPt, cursorPos );
 8006                  }
 8007                  prevPt = currentPt;
 8008  
 8009                  // In liveDraw we miss the mouse up
 8010                  if( GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_LAYERED) {
 8011  
 8012                      if((GetAsyncKeyState(VK_LBUTTON) & 0x8000) == 0) {
 8013  
 8014                          OutputDebug(L"LIVE_DRAW missed mouse up. Sending synthetic.\n");
 8015                          SendMessage(hWnd, WM_LBUTTONUP, wParam, lParam);
 8016                      }
 8017                  }
 8018  
 8019              } else {
 8020  
 8021                  cursorPos.x = LOWORD( lParam );
 8022                  cursorPos.y = HIWORD( lParam );
 8023                  InvalidateRect( hWnd, NULL, FALSE );
 8024              }
 8025          } else if( g_Zoomed && (g_TypeMode != TypeModeOff) && !g_HaveTyped ) {
 8026  
 8027              ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen );
 8028              textPt.x = prevPt.x = LOWORD( lParam );
 8029              textPt.y = prevPt.y = HIWORD( lParam );
 8030  
 8031              // Draw the typing cursor
 8032              DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc, true );
 8033              prevPt = textPt;
 8034              InvalidateRect( hWnd, NULL, FALSE );
 8035          }
 8036  #if 0
 8037          {
 8038              static int index = 0;
 8039              OutputDebug( L"%d: foreground: %x focus: %x (hwnd: %x)\n",
 8040                  index++, (DWORD) PtrToUlong(GetForegroundWindow()),  PtrToUlong(GetFocus()), PtrToUlong(hWnd));
 8041          }
 8042  #endif
 8043          return TRUE;
 8044  
 8045      case WM_LBUTTONDOWN:
 8046          g_StraightDirection = 0;
 8047  
 8048          if( g_Zoomed && (g_TypeMode == TypeModeOff) && zoomTelescopeTarget == zoomLevel ) {
 8049  
 8050              OutputDebug(L"LBUTTONDOWN: drawing\n");
 8051  
 8052              // Save current bitmap to undo history
 8053              if( g_HaveDrawn ) {
 8054  
 8055                  RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
 8056              }
 8057  
 8058              // don't push undo if we sent this to ourselves for a pen resize
 8059              if( wParam != -1 ) {
 8060  
 8061                  PushDrawUndo( hdcScreenCompat, &drawUndoList, width, height );
 8062  
 8063              } else {
 8064  
 8065                  wParam = 0;
 8066              }
 8067  
 8068              // Are we in pen mode on a tablet?
 8069              lParam = ScalePenPosition( zoomLevel, &monInfo, boundRc,
 8070                          message, lParam);
 8071  
 8072              if (lParam == 0) {
 8073  
 8074                  // Drop it
 8075                  break;
 8076  
 8077              } else if( g_Drawing ) {
 8078  
 8079                  // is the user drawing a rectangle?
 8080                  if( wParam & MK_CONTROL ||
 8081                      wParam & MK_SHIFT ||
 8082                      GetKeyState( VK_TAB ) < 0 ) {
 8083  
 8084                      // Restore area where cursor was previously
 8085                      RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
 8086  
 8087                      if( wParam & MK_SHIFT && wParam & MK_CONTROL )
 8088                          g_DrawingShape = DRAW_ARROW;
 8089                      else if( wParam & MK_CONTROL )
 8090                          g_DrawingShape = DRAW_RECTANGLE;
 8091                      else if( wParam & MK_SHIFT )
 8092                          g_DrawingShape = DRAW_LINE;
 8093                      else
 8094                          g_DrawingShape = DRAW_ELLIPSE;
 8095                      g_RectangleAnchor.x = LOWORD(lParam);
 8096                      g_RectangleAnchor.y = HIWORD(lParam);
 8097                      SetRect(&g_rcRectangle, g_RectangleAnchor.x, g_RectangleAnchor.y,
 8098                              g_RectangleAnchor.x, g_RectangleAnchor.y);
 8099  
 8100                  } else {
 8101  
 8102                      Gdiplus::Graphics	dstGraphics(hdcScreenCompat);
 8103                      if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
 8104                      {
 8105                          dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
 8106                      }
 8107                      Gdiplus::Color	color = ColorFromColorRef(g_PenColor);
 8108                      Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
 8109                      Gdiplus::GraphicsPath path;
 8110                      pen.SetLineJoin(Gdiplus::LineJoinRound);
 8111                      path.AddLine(static_cast<INT>(prevPt.x), prevPt.y, prevPt.x, prevPt.y);
 8112                      dstGraphics.DrawPath(&pen, &path);
 8113                  }
 8114                  g_Tracing = TRUE;
 8115                  SetROP2( hdcScreenCompat, R2_COPYPEN );
 8116                  prevPt.x = LOWORD(lParam);
 8117                  prevPt.y = HIWORD(lParam);
 8118                  g_HaveDrawn = TRUE;
 8119  
 8120              } else {
 8121  
 8122                  OutputDebug(L"Tracing on\n");
 8123  
 8124                  // Turn on drawing
 8125                  if( !g_HaveDrawn ) {
 8126  
 8127                      // refresh drawing bitmap with original screen image
 8128                      BitBlt(hdcScreenCompat, 0, 0, bmp.bmWidth,
 8129                          bmp.bmHeight, hdcScreenSaveCompat, 0, 0, SRCCOPY|CAPTUREBLT );
 8130                      g_HaveDrawn = TRUE;
 8131                  }
 8132                  DeleteObject( hDrawingPen );
 8133                  hDrawingPen = CreatePen(PS_SOLID, g_PenWidth, g_PenColor & 0xFFFFFF);
 8134                  SelectObject( hdcScreenCompat, hDrawingPen );
 8135  
 8136                  // is the user drawing a rectangle?
 8137                  if( wParam & MK_CONTROL && g_Drawing ) {
 8138  
 8139                      // Restore area where cursor was previously
 8140                      RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
 8141  
 8142                      // Configure rectangle drawing
 8143                      g_DrawingShape = TRUE;
 8144                      g_RectangleAnchor.x = LOWORD(lParam);
 8145                      g_RectangleAnchor.y = HIWORD(lParam);
 8146                      SetRect(&g_rcRectangle, g_RectangleAnchor.x, g_RectangleAnchor.y,
 8147                          g_RectangleAnchor.x, g_RectangleAnchor.y);
 8148                      OutputDebug( L"RECTANGLE: %d, %d\n", prevPt.x, prevPt.y );
 8149  
 8150                  } else {
 8151  
 8152                      prevPt.x = LOWORD( lParam );
 8153                      prevPt.y = HIWORD( lParam );
 8154                      SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
 8155  
 8156                      Gdiplus::Graphics	dstGraphics(hdcScreenCursorCompat);
 8157                      if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
 8158                      {
 8159                          dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
 8160                      }
 8161                      Gdiplus::Color	color = ColorFromColorRef(g_PenColor);
 8162                      Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
 8163                      Gdiplus::GraphicsPath path;
 8164                      pen.SetLineJoin(Gdiplus::LineJoinRound);
 8165                      path.AddLine(static_cast<INT>(prevPt.x), prevPt.y, prevPt.x, prevPt.y);
 8166                      dstGraphics.DrawPath(&pen, &path);
 8167                  }
 8168                  InvalidateRect( hWnd, NULL, FALSE );
 8169  
 8170                  // If we're in live zoom, make the drawing pen larger to compensate
 8171                  if( g_ZoomOnLiveZoom && forcePenResize )
 8172                  {
 8173                      forcePenResize = FALSE;
 8174  
 8175                      ResizePen( hWnd, hdcScreenCompat, hdcScreenCursorCompat, prevPt, g_Tracing,
 8176                          &g_Drawing, g_LiveZoomLevel, FALSE, min( static_cast<int>(g_LiveZoomLevel * g_RootPenWidth),
 8177                          static_cast<int>(g_LiveZoomLevel * MAX_PEN_WIDTH) ) );
 8178                      OutputDebug( L"LIVEZOOM_DRAW: zoomLevel: %d rootPenWidth: %d penWidth: %d\n",
 8179                          static_cast<int>(g_LiveZoomLevel), g_RootPenWidth, g_PenWidth );
 8180                  }
 8181                  else if( !g_ZoomOnLiveZoom && forcePenResize )
 8182                  {
 8183                      forcePenResize = FALSE;
 8184                      // Scale pen down to root for regular drawing mode
 8185                      ResizePen( hWnd, hdcScreenCompat, hdcScreenCursorCompat, prevPt, g_Tracing,
 8186                          &g_Drawing, g_LiveZoomLevel, FALSE, g_RootPenWidth );
 8187                  }
 8188                  g_Drawing = TRUE;
 8189  
 8190                  EnableDisableStickyKeys( FALSE );
 8191                  OutputDebug( L"LBUTTONDOWN: %d, %d\n", prevPt.x, prevPt.y );
 8192  
 8193                  // Constrain the mouse to the visible region
 8194                  boundRc = BoundMouse( zoomLevel, &monInfo, width, height, &cursorPos );
 8195              }
 8196          } else if( g_TypeMode != TypeModeOff ) {
 8197  
 8198              if( !g_HaveTyped ) {
 8199  
 8200                  g_HaveTyped = TRUE;
 8201  
 8202              } else {
 8203  
 8204                  SendMessage( hWnd, WM_USER_TYPING_OFF, 0, 0 );
 8205              }
 8206          }
 8207          return TRUE;
 8208  
 8209      case WM_LBUTTONUP:
 8210          OutputDebug(L"LBUTTONUP: zoomed: %d drawing: %d tracing: %d\n",
 8211              g_Zoomed, g_Drawing, g_Tracing);
 8212  
 8213          if( g_Zoomed && g_Drawing && g_Tracing ) {
 8214  
 8215              // Are we in pen mode on a tablet?
 8216              lParam = ScalePenPosition( zoomLevel, &monInfo, boundRc,
 8217                          message, lParam);
 8218              OutputDebug(L"LBUTTONUP: %d, %d\n", LOWORD(lParam), HIWORD(lParam));
 8219              if (lParam == 0) {
 8220  
 8221                  // Drop it
 8222                  break;
 8223              }
 8224  
 8225              POINT adjustPos;
 8226              adjustPos.x = LOWORD(lParam);
 8227              adjustPos.y = HIWORD(lParam);
 8228              if( g_StraightDirection == -1 ) {
 8229  
 8230                  adjustPos.x = prevPt.x;
 8231  
 8232              } else {
 8233  
 8234                  adjustPos.y = prevPt.y;
 8235              }
 8236              lParam = MAKELPARAM( adjustPos.x, adjustPos.y );
 8237  
 8238              if( !g_DrawingShape ) {
 8239  
 8240                  // If the point has changed, draw a line to it
 8241                  if (!PEN_COLOR_HIGHLIGHT(g_PenColor))
 8242                  {
 8243                      if (prevPt.x != LOWORD(lParam) || prevPt.y != HIWORD(lParam))
 8244                      {
 8245                          Gdiplus::Graphics dstGraphics(hdcScreenCompat);
 8246                          if ((GetWindowLong(g_hWndMain, GWL_EXSTYLE) & WS_EX_LAYERED) == 0)
 8247                          {
 8248                              dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
 8249                          }
 8250                          Gdiplus::Color color = ColorFromColorRef(g_PenColor);
 8251                          Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
 8252                          Gdiplus::GraphicsPath path;
 8253                          pen.SetLineJoin(Gdiplus::LineJoinRound);
 8254                          path.AddLine(prevPt.x, prevPt.y, LOWORD(lParam), HIWORD(lParam));
 8255                          dstGraphics.DrawPath(&pen, &path);
 8256                      }
 8257                      // Draw a dot at the current point, if the point hasn't changed
 8258                      else
 8259                      {
 8260                          MoveToEx(hdcScreenCompat, prevPt.x, prevPt.y, NULL);
 8261                          LineTo(hdcScreenCompat, LOWORD(lParam), HIWORD(lParam));
 8262                          InvalidateRect(hWnd, NULL, FALSE);
 8263                      }
 8264                  }
 8265                  prevPt.x = LOWORD( lParam );
 8266                  prevPt.y = HIWORD( lParam );
 8267  
 8268                  if ((g_PenColor & 0xFFFFFF) == COLOR_BLUR) {
 8269  
 8270                      RestoreCursorArea(hdcScreenCompat, hdcScreenCursorCompat, prevPt);
 8271                  }
 8272                  SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
 8273                  DrawCursor( hdcScreenCompat, prevPt, zoomLevel, width, height );
 8274  
 8275              } else if (g_rcRectangle.top != g_rcRectangle.bottom ||
 8276                          g_rcRectangle.left != g_rcRectangle.right ) {
 8277  
 8278                  // erase previous
 8279                  if (!PEN_COLOR_HIGHLIGHT(g_PenColor))
 8280                  {
 8281                      SetROP2(hdcScreenCompat, R2_NOTXORPEN);
 8282                      DrawShape(g_DrawingShape, hdcScreenCompat, &g_rcRectangle);
 8283                  }
 8284  
 8285                  // Draw the final shape
 8286                  HBRUSH hBrush = static_cast<HBRUSH>(GetStockObject( NULL_BRUSH ));
 8287                  HBRUSH oldHbrush = static_cast<HBRUSH>(SelectObject( hdcScreenCompat, hBrush ));
 8288                  SetROP2( hdcScreenCompat, R2_COPYPEN );
 8289  
 8290                  // smooth line
 8291                  if( g_SnapToGrid ) {
 8292  
 8293                      if( g_DrawingShape == DRAW_LINE ||
 8294                          g_DrawingShape == DRAW_ARROW ) {
 8295  
 8296                          if( abs(g_rcRectangle.bottom - g_rcRectangle.top) <
 8297                              abs(g_rcRectangle.right - g_rcRectangle.left)/10 ) {
 8298  
 8299                              g_rcRectangle.bottom = g_rcRectangle.top-1;
 8300                          }
 8301                          if( abs(g_rcRectangle.right - g_rcRectangle.left) <
 8302                              abs(g_rcRectangle.bottom - g_rcRectangle.top)/10 ) {
 8303  
 8304                              g_rcRectangle.right = g_rcRectangle.left-1;
 8305                          }
 8306                      }
 8307                  }
 8308                  // Draw final one using Gdi+
 8309                  DrawShape( g_DrawingShape, hdcScreenCompat, &g_rcRectangle, true );
 8310  
 8311                  InvalidateRect( hWnd, NULL, FALSE );
 8312                  DeleteObject( hBrush );
 8313                  SelectObject( hdcScreenCompat, oldHbrush );
 8314  
 8315                  prevPt.x = LOWORD( lParam );
 8316                  prevPt.y = HIWORD( lParam );
 8317                  SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
 8318              }
 8319              g_Tracing = FALSE;
 8320              g_DrawingShape = FALSE;
 8321              OutputDebug( L"LBUTTONUP:" );
 8322          }
 8323          return TRUE;
 8324  
 8325      case WM_GETMINMAXINFO:
 8326  
 8327          reinterpret_cast<MINMAXINFO *>(lParam)->ptMaxSize.x = width;
 8328          reinterpret_cast<MINMAXINFO*>(lParam)->ptMaxSize.y = height;
 8329          reinterpret_cast<MINMAXINFO*>(lParam)->ptMaxPosition.x = 0;
 8330          reinterpret_cast<MINMAXINFO*>(lParam)->ptMaxPosition.y = 0;
 8331          return TRUE;
 8332  
 8333      case WM_USER_TYPING_OFF:	{
 8334  
 8335          if( g_TypeMode != TypeModeOff ) {
 8336  
 8337              g_TypeMode = TypeModeOff;
 8338              ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen );
 8339              InvalidateRect( hWnd, NULL, FALSE );
 8340              DeleteTypedText( &typedKeyList );
 8341  
 8342              // 1 means don't reset the cursor. We get that for font resizing
 8343              // Only move the cursor if we're drawing, else the screen moves to center
 8344              // on the new cursor position
 8345              if( wParam != 1 && g_Drawing ) {
 8346  
 8347                  prevPt.x = cursorRc.left;
 8348                  prevPt.y = cursorRc.top;
 8349                  SetCursorPos( monInfo.rcMonitor.left + prevPt.x,
 8350                          monInfo.rcMonitor.top + prevPt.y );
 8351  
 8352                  SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
 8353                  SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
 8354  
 8355              } else if( !g_Drawing) {
 8356  
 8357                  // FIX: would be nice to reset cursor so screen doesn't move
 8358                  prevPt = textStartPt;
 8359                  SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
 8360                  SetCursorPos( prevPt.x, prevPt.y );
 8361                  SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
 8362              }
 8363          }
 8364          }
 8365          return TRUE;
 8366  
 8367      case WM_USER_TRAY_ACTIVATE:
 8368  
 8369          switch( lParam ) {
 8370          case WM_RBUTTONUP:
 8371          case WM_LBUTTONUP:
 8372          case WM_CONTEXTMENU:
 8373          {
 8374              // Set the foreground window so the menu can be closed by clicking elsewhere when
 8375              // opened via right click, and so keyboard navigation works when opened with the menu
 8376              // key or Shift-F10.
 8377              SetForegroundWindow( hWndOptions ? hWndOptions : hWnd );
 8378  
 8379              // Pop up context menu
 8380              POINT pt;
 8381              GetCursorPos( &pt );
 8382              hPopupMenu = CreatePopupMenu();
 8383              if(!g_StartedByPowerToys) {
 8384                  // Exiting will happen through disabling in PowerToys, not the context menu.
 8385                  InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDCANCEL, L"E&xit" );
 8386                  InsertMenu( hPopupMenu, 0, MF_BYPOSITION|MF_SEPARATOR, 0, NULL );
 8387              }
 8388              InsertMenu( hPopupMenu, 0, MF_BYPOSITION | ( g_RecordToggle ? MF_CHECKED : 0 ), IDC_RECORD, L"&Record" );
 8389              InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDC_ZOOM, L"&Zoom" );
 8390              InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDC_DRAW, L"&Draw" );
 8391              InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDC_BREAK, L"&Break Timer" );
 8392              if(!g_StartedByPowerToys) {
 8393                  // When started by PowerToys, options are configured through the PowerToys Settings.
 8394                  InsertMenu( hPopupMenu, 0, MF_BYPOSITION|MF_SEPARATOR, 0, NULL );
 8395                  InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDC_OPTIONS, L"&Options" );
 8396              }
 8397              // Apply dark mode theme to the menu
 8398              ApplyDarkModeToMenu( hPopupMenu );
 8399              TrackPopupMenu( hPopupMenu, 0, pt.x , pt.y, 0, hWnd, NULL );
 8400              DestroyMenu( hPopupMenu );
 8401              break;
 8402          }
 8403          case WM_LBUTTONDBLCLK:
 8404              if( !g_TimerActive ) {
 8405  
 8406                  SendMessage( hWnd, WM_COMMAND, IDC_OPTIONS, 0 );
 8407  
 8408              } else {
 8409  
 8410                  SetForegroundWindow( hWnd );
 8411              }
 8412              break;
 8413          }
 8414          break;
 8415  
 8416      case WM_USER_STOP_RECORDING:
 8417          StopRecording();
 8418          break;
 8419  
 8420      case WM_USER_SAVE_CURSOR:
 8421          if( g_Zoomed == TRUE )
 8422          {
 8423              GetCursorPos( &savedCursorPos );
 8424              if( g_Drawing == TRUE )
 8425              {
 8426                  ClipCursor( NULL );
 8427              }
 8428          }
 8429          break;
 8430  
 8431      case WM_USER_RESTORE_CURSOR:
 8432          if( g_Zoomed == TRUE )
 8433          {
 8434              if( g_Drawing == TRUE )
 8435              {
 8436                  boundRc = BoundMouse( zoomLevel, &monInfo, width, height, &cursorPos );
 8437              }
 8438              SetCursorPos( savedCursorPos.x, savedCursorPos.y );
 8439          }
 8440          break;
 8441  
 8442      case WM_USER_EXIT_MODE:
 8443          if( g_Zoomed )
 8444          {
 8445              // Turn off
 8446              if( g_TypeMode != TypeModeOff )
 8447              {
 8448                  SendMessage( hWnd, WM_USER_TYPING_OFF, 0, 0 );
 8449              }
 8450              else if( !g_Drawing )
 8451              {
 8452                  // Turn off
 8453                  PostMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 );
 8454              }
 8455              else
 8456              {
 8457                  if( !g_Tracing )
 8458                  {
 8459                      RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
 8460  
 8461                      // Ensure the cursor area is painted before returning
 8462                      InvalidateRect( hWnd, NULL, FALSE );
 8463                      UpdateWindow( hWnd );
 8464  
 8465                      // Make the magnified cursor visible again if LiveDraw is on in LiveZoom
 8466                      if( GetWindowLong( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED )
 8467                      {
 8468                          if( IsWindowVisible( g_hWndLiveZoom ) )
 8469                          {
 8470                              SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, TRUE, 0 );
 8471                          }
 8472                      }
 8473                  }
 8474                  if( zoomLevel != 1 )
 8475                  {
 8476                      // Restore the cursor position to prevent moving the view in static zoom
 8477                      SetCursorPos( monInfo.rcMonitor.left + cursorPos.x, monInfo.rcMonitor.top + cursorPos.y );
 8478                  }
 8479                  g_Drawing = FALSE;
 8480                  g_Tracing = FALSE;
 8481                  EnableDisableStickyKeys( TRUE );
 8482                  SendMessage( hWnd, WM_USER_TYPING_OFF, 0, 0 );
 8483  
 8484                  // Unclip cursor
 8485                  ClipCursor( NULL );
 8486              }
 8487          }
 8488          else if( g_TimerActive )
 8489          {
 8490              // Turn off
 8491              PostMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 );
 8492          }
 8493          break;
 8494  
 8495      case WM_USER_RELOAD_SETTINGS:
 8496      {
 8497          // Reload the settings. This message is called from PowerToys after a setting is changed by the user.
 8498          reg.ReadRegSettings(RegSettings);
 8499  
 8500          // Refresh dark mode state after loading theme override from registry
 8501          RefreshDarkModeState();
 8502  
 8503          if (g_RecordingFormat == RecordingFormat::GIF)
 8504          {
 8505              g_RecordScaling = g_RecordScalingGIF;
 8506              g_RecordFrameRate = RECORDING_FORMAT_GIF_DEFAULT_FRAMERATE;
 8507          }
 8508          else
 8509          {
 8510              g_RecordScaling = g_RecordScalingMP4;
 8511              g_RecordFrameRate = RECORDING_FORMAT_MP4_DEFAULT_FRAMERATE;
 8512          }
 8513  
 8514          // Apply tray icon setting
 8515          EnableDisableTrayIcon(hWnd, g_ShowTrayIcon);
 8516  
 8517          // This is also called by ZoomIt when it starts and loads the Settings. Opacity is added after loading from registry, so we use the same pattern.
 8518          if ((g_PenColor >> 24) == 0)
 8519          {
 8520              g_PenColor |= 0xFF << 24;
 8521          }
 8522  
 8523          // Apply hotkey settings
 8524          UnregisterAllHotkeys(hWnd);
 8525          g_ToggleMod = GetKeyMod(g_ToggleKey);
 8526          g_LiveZoomToggleMod = GetKeyMod(g_LiveZoomToggleKey);
 8527          g_DrawToggleMod = GetKeyMod(g_DrawToggleKey);
 8528          g_BreakToggleMod = GetKeyMod(g_BreakToggleKey);
 8529          g_DemoTypeToggleMod = GetKeyMod(g_DemoTypeToggleKey);
 8530          g_SnipToggleMod = GetKeyMod(g_SnipToggleKey);
 8531          g_RecordToggleMod = GetKeyMod(g_RecordToggleKey);
 8532          BOOL showOptions = FALSE;
 8533          if (g_ToggleKey)
 8534          {
 8535              if (!RegisterHotKey(hWnd, ZOOM_HOTKEY, g_ToggleMod, g_ToggleKey & 0xFF))
 8536              {
 8537                  MessageBox(hWnd, L"The specified zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR);
 8538                  showOptions = TRUE;
 8539              }
 8540          }
 8541          if (g_LiveZoomToggleKey)
 8542          {
 8543              if (!RegisterHotKey(hWnd, LIVE_HOTKEY, g_LiveZoomToggleMod, g_LiveZoomToggleKey & 0xFF) ||
 8544                  !RegisterHotKey(hWnd, LIVE_DRAW_HOTKEY, g_LiveZoomToggleMod ^ MOD_SHIFT, g_LiveZoomToggleKey & 0xFF))
 8545              {
 8546                  MessageBox(hWnd, L"The specified live-zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR);
 8547                  showOptions = TRUE;
 8548              }
 8549          }
 8550          if (g_DrawToggleKey)
 8551          {
 8552              if (!RegisterHotKey(hWnd, DRAW_HOTKEY, g_DrawToggleMod, g_DrawToggleKey & 0xFF))
 8553              {
 8554                  MessageBox(hWnd, L"The specified draw w/out zoom hotkey is already in use.\nSelect a different draw w/out zoom hotkey.", APPNAME, MB_ICONERROR);
 8555                  showOptions = TRUE;
 8556              }
 8557          }
 8558          if (g_BreakToggleKey)
 8559          {
 8560              if (!RegisterHotKey(hWnd, BREAK_HOTKEY, g_BreakToggleMod, g_BreakToggleKey & 0xFF))
 8561              {
 8562                  MessageBox(hWnd, L"The specified break timer hotkey is already in use.\nSelect a different break timer hotkey.", APPNAME, MB_ICONERROR);
 8563                  showOptions = TRUE;
 8564              }
 8565          }
 8566          if (g_DemoTypeToggleKey)
 8567          {
 8568              if (!RegisterHotKey(hWnd, DEMOTYPE_HOTKEY, g_DemoTypeToggleMod, g_DemoTypeToggleKey & 0xFF) ||
 8569                  !RegisterHotKey(hWnd, DEMOTYPE_RESET_HOTKEY, (g_DemoTypeToggleMod ^ MOD_SHIFT), g_DemoTypeToggleKey & 0xFF))
 8570              {
 8571                  MessageBox(hWnd, L"The specified live-type hotkey is already in use.\nSelect a different live-type hotkey.", APPNAME, MB_ICONERROR);
 8572                  showOptions = TRUE;
 8573              }
 8574          }
 8575          if (g_SnipToggleKey)
 8576          {
 8577              if (!RegisterHotKey(hWnd, SNIP_HOTKEY, g_SnipToggleMod, g_SnipToggleKey & 0xFF) ||
 8578                  !RegisterHotKey(hWnd, SNIP_SAVE_HOTKEY, (g_SnipToggleMod ^ MOD_SHIFT), g_SnipToggleKey & 0xFF))
 8579              {
 8580                  MessageBox(hWnd, L"The specified snip hotkey is already in use.\nSelect a different snip hotkey.", APPNAME, MB_ICONERROR);
 8581                  showOptions = TRUE;
 8582              }
 8583          }
 8584          if (g_RecordToggleKey)
 8585          {
 8586              if (!RegisterHotKey(hWnd, RECORD_HOTKEY, g_RecordToggleMod | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) ||
 8587                  !RegisterHotKey(hWnd, RECORD_CROP_HOTKEY, (g_RecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) ||
 8588                  !RegisterHotKey(hWnd, RECORD_WINDOW_HOTKEY, (g_RecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF))
 8589              {
 8590                  MessageBox(hWnd, L"The specified record hotkey is already in use.\nSelect a different record hotkey.", APPNAME, MB_ICONERROR);
 8591                  showOptions = TRUE;
 8592              }
 8593          }
 8594          // Register CTRL+8 for GIF recording and CTRL+ALT+8 for GIF window recording
 8595          if (!RegisterHotKey(hWnd, RECORD_GIF_HOTKEY, MOD_CONTROL | MOD_NOREPEAT, '8') ||
 8596              !RegisterHotKey(hWnd, RECORD_GIF_WINDOW_HOTKEY, MOD_CONTROL | MOD_ALT | MOD_NOREPEAT, '8'))
 8597          {
 8598              MessageBox(hWnd, L"The specified GIF recording hotkey is already in use.\nSelect a different GIF recording hotkey.", APPNAME, MB_ICONERROR);
 8599              showOptions = TRUE;
 8600          }
 8601          if (showOptions)
 8602          {
 8603              // To open the PowerToys settings in the ZoomIt page.
 8604              SendMessage(hWnd, WM_COMMAND, IDC_OPTIONS, 0);
 8605          }
 8606          break;
 8607      }
 8608      case WM_COMMAND:
 8609  
 8610          switch(LOWORD( wParam )) {
 8611  
 8612          case IDC_SAVE_CROP:
 8613          case IDC_SAVE:
 8614          {
 8615              POINT local_savedCursorPos{};
 8616              if( lParam != SHALLOW_ZOOM )
 8617              {
 8618                  GetCursorPos(&local_savedCursorPos);
 8619              }
 8620  
 8621              // Determine the user's desired save area in zoomed viewport coordinates.
 8622              // This will be the entire viewport if the user does not select a crop
 8623              // rectangle.
 8624              int copyX = 0, copyY = 0, copyWidth = width, copyHeight = height;
 8625  
 8626              if ( LOWORD( wParam ) == IDC_SAVE_CROP )
 8627              {
 8628                  g_RecordCropping = TRUE;
 8629                  SelectRectangle selectRectangle;
 8630                  if( !selectRectangle.Start( hWnd ) )
 8631                  {
 8632                      g_RecordCropping = FALSE;
 8633                      if( lParam != SHALLOW_ZOOM )
 8634                      {
 8635                          SetCursorPos(local_savedCursorPos.x, local_savedCursorPos.y);
 8636                      }
 8637                      break;
 8638                  }
 8639  
 8640                  auto copyRc = selectRectangle.SelectedRect();
 8641                  selectRectangle.Stop();
 8642                  g_RecordCropping = FALSE;
 8643  
 8644                  copyX = copyRc.left;
 8645                  copyY = copyRc.top;
 8646                  copyWidth = copyRc.right - copyRc.left;
 8647                  copyHeight = copyRc.bottom - copyRc.top;
 8648              }
 8649              OutputDebug( L"***x: %d, y: %d, width: %d, height: %d\n", copyX, copyY, copyWidth, copyHeight );
 8650  
 8651              RECT oldClipRect{};
 8652              GetClipCursor( &oldClipRect );
 8653              ClipCursor( NULL );
 8654  
 8655              // Translate the viewport selection into coordinates for the 1:1 source
 8656              // bitmap hdcScreenCompat.
 8657              int viewportX, viewportY;
 8658              GetZoomedTopLeftCoordinates(
 8659                  zoomLevel, &cursorPos, &viewportX, width, &viewportY, height );
 8660  
 8661              int saveX = viewportX + static_cast<int>( copyX / zoomLevel );
 8662              int saveY = viewportY + static_cast<int>( copyY / zoomLevel );
 8663              int saveWidth = static_cast<int>( copyWidth / zoomLevel );
 8664              int saveHeight = static_cast<int>( copyHeight / zoomLevel );
 8665  
 8666              // Create a pixel-accurate copy of the desired area from the source bitmap.
 8667              wil::unique_hdc hdcActualSize( CreateCompatibleDC( hdcScreen ) );
 8668              wil::unique_hbitmap hbmActualSize(
 8669                  CreateCompatibleBitmap( hdcScreen, saveWidth, saveHeight ) );
 8670              // Note: we do not need to restore the existing context later. The objects
 8671              // are transient and not reused.
 8672              SelectObject( hdcActualSize.get(), hbmActualSize.get() );
 8673  
 8674              // Perform a direct 1:1 copy from the backing bitmap.
 8675              BitBlt( hdcActualSize.get(),
 8676                      0, 0,
 8677                      saveWidth, saveHeight,
 8678                      hdcScreenCompat,
 8679                      saveX, saveY,
 8680                      SRCCOPY | CAPTUREBLT );
 8681  
 8682              // Open the Save As dialog and capture the desired file path and whether to
 8683              // save the zoomed display or the source bitmap pixels.
 8684              g_bSaveInProgress = true;
 8685  
 8686              // Get a unique filename suggestion
 8687              auto suggestedName = GetUniqueScreenshotFilename();
 8688  
 8689              // Create modern IFileSaveDialog
 8690              auto saveDialog = wil::CoCreateInstance<IFileSaveDialog>( CLSID_FileSaveDialog );
 8691  
 8692              FILEOPENDIALOGOPTIONS options;
 8693              if( SUCCEEDED( saveDialog->GetOptions( &options ) ) )
 8694                  saveDialog->SetOptions( options | FOS_FORCEFILESYSTEM | FOS_OVERWRITEPROMPT );
 8695  
 8696              // Set file types - index is 1-based when retrieved via GetFileTypeIndex
 8697              COMDLG_FILTERSPEC fileTypes[] = {
 8698                  { L"Zoomed PNG", L"*.png" },
 8699                  { L"Actual size PNG", L"*.png" }
 8700              };
 8701              saveDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
 8702              saveDialog->SetFileTypeIndex( 1 ); // Default to "Zoomed PNG"
 8703              saveDialog->SetDefaultExtension( L"png" );
 8704              saveDialog->SetFileName( suggestedName.c_str() );
 8705              saveDialog->SetTitle( L"ZoomIt: Save Zoomed Screen..." );
 8706  
 8707              // Set default folder to the last save location if available
 8708              if( !g_ScreenshotSaveLocation.empty() )
 8709              {
 8710                  std::filesystem::path lastPath( g_ScreenshotSaveLocation );
 8711                  if( lastPath.has_parent_path() )
 8712                  {
 8713                      wil::com_ptr<IShellItem> folderItem;
 8714                      if( SUCCEEDED( SHCreateItemFromParsingName( lastPath.parent_path().c_str(),
 8715                          nullptr, IID_PPV_ARGS( &folderItem ) ) ) )
 8716                      {
 8717                          saveDialog->SetFolder( folderItem.get() );
 8718                      }
 8719                  }
 8720              }
 8721  
 8722              OpenSaveDialogEvents* pEvents = new OpenSaveDialogEvents();
 8723              DWORD dwCookie = 0;
 8724              saveDialog->Advise(pEvents, &dwCookie);
 8725  
 8726              UINT selectedFilterIndex = 1;
 8727              std::wstring selectedFilePath;
 8728  
 8729              if( SUCCEEDED( saveDialog->Show( hWnd ) ) )
 8730              {
 8731                  wil::com_ptr<IShellItem> resultItem;
 8732                  if( SUCCEEDED( saveDialog->GetResult( &resultItem ) ) )
 8733                  {
 8734                      wil::unique_cotaskmem_string pathStr;
 8735                      if( SUCCEEDED( resultItem->GetDisplayName( SIGDN_FILESYSPATH, &pathStr ) ) )
 8736                      {
 8737                          selectedFilePath = pathStr.get();
 8738                      }
 8739                  }
 8740                  saveDialog->GetFileTypeIndex( &selectedFilterIndex );
 8741              }
 8742  
 8743              saveDialog->Unadvise(dwCookie);
 8744              pEvents->Release();
 8745  
 8746              if( !selectedFilePath.empty() )
 8747              {
 8748                  std::wstring targetFilePath = selectedFilePath;
 8749                  if( targetFilePath.find(L'.') == std::wstring::npos )
 8750                  {
 8751                      targetFilePath += L".png";
 8752                  }
 8753  
 8754                  if( selectedFilterIndex == 2 )
 8755                  {
 8756                      // Save at actual size.
 8757                      SavePng( targetFilePath.c_str(), hbmActualSize.get() );
 8758                  }
 8759                  else
 8760                  {
 8761                      // Save zoomed-in image at screen resolution.
 8762  #if SCALE_HALFTONE
 8763                      const int bltMode = HALFTONE;
 8764  #else
 8765                      // Use HALFTONE for better quality when smooth image is enabled
 8766                      const int bltMode = g_SmoothImage ? HALFTONE : COLORONCOLOR;
 8767  #endif
 8768                      // Recreate the zoomed-in view by upscaling from our source bitmap.
 8769                      wil::unique_hdc hdcZoomed( CreateCompatibleDC(hdcScreen) );
 8770                      wil::unique_hbitmap hbmZoomed(
 8771                          CreateCompatibleBitmap( hdcScreen, copyWidth, copyHeight ) );
 8772                      SelectObject( hdcZoomed.get(), hbmZoomed.get() );
 8773  
 8774                      SetStretchBltMode( hdcZoomed.get(), bltMode );
 8775  
 8776                      StretchBlt( hdcZoomed.get(),
 8777                                  0, 0,
 8778                                  copyWidth, copyHeight,
 8779                                  hdcActualSize.get(),
 8780                                  0, 0,
 8781                                  saveWidth, saveHeight,
 8782                                  SRCCOPY | CAPTUREBLT );
 8783  
 8784                      SavePng(targetFilePath.c_str(), hbmZoomed.get());
 8785                  }
 8786  
 8787                  // Remember the save location for next time and persist to registry
 8788                  g_ScreenshotSaveLocation = targetFilePath;
 8789                  wcsncpy_s(g_ScreenshotSaveLocationBuffer, g_ScreenshotSaveLocation.c_str(), _TRUNCATE);
 8790                  reg.WriteRegSettings(RegSettings);
 8791              }
 8792              g_bSaveInProgress = false;
 8793  
 8794              if( lParam != SHALLOW_ZOOM )
 8795              {
 8796                  SetCursorPos( local_savedCursorPos.x, local_savedCursorPos.y );
 8797              }
 8798              ClipCursor( &oldClipRect );
 8799  
 8800              break;
 8801          }
 8802  
 8803          case IDC_COPY_CROP:
 8804          case IDC_COPY: {
 8805              HBITMAP		hSaveBitmap;
 8806              HDC			hSaveDc;
 8807              int         copyX, copyY;
 8808              int         copyWidth, copyHeight;
 8809  
 8810              if( LOWORD( wParam ) == IDC_COPY_CROP )
 8811              {
 8812                  g_RecordCropping = TRUE;
 8813                  POINT local_savedCursorPos{};
 8814                  if( lParam != SHALLOW_ZOOM )
 8815                  {
 8816                      GetCursorPos(&local_savedCursorPos);
 8817                  }
 8818                  SelectRectangle selectRectangle;
 8819                  if( !selectRectangle.Start( hWnd ) )
 8820                  {
 8821                      g_RecordCropping = FALSE;
 8822                      break;
 8823                  }
 8824                  auto copyRc = selectRectangle.SelectedRect();
 8825                  selectRectangle.Stop();
 8826                  if( lParam != SHALLOW_ZOOM )
 8827                  {
 8828                      SetCursorPos(local_savedCursorPos.x, local_savedCursorPos.y);
 8829                  }
 8830                  g_RecordCropping = FALSE;
 8831  
 8832                  copyX = copyRc.left;
 8833                  copyY = copyRc.top;
 8834                  copyWidth = copyRc.right - copyRc.left;
 8835                  copyHeight = copyRc.bottom - copyRc.top;
 8836              }
 8837              else
 8838              {
 8839                  copyX = 0;
 8840                  copyY = 0;
 8841                  copyWidth = width;
 8842                  copyHeight = height;
 8843              }
 8844              OutputDebug( L"***x: %d, y: %d, width: %d, height: %d\n", copyX, copyY, copyWidth, copyHeight );
 8845  
 8846              hSaveBitmap = CreateCompatibleBitmap( hdcScreen, copyWidth, copyHeight );
 8847              hSaveDc = CreateCompatibleDC( hdcScreen );
 8848              SelectObject( hSaveDc, hSaveBitmap );
 8849  #if SCALE_HALFTONE
 8850              SetStretchBltMode( hSaveDc, HALFTONE );
 8851  #else
 8852              // Use HALFTONE for better quality when smooth image is enabled
 8853              if (g_SmoothImage) {
 8854                  SetStretchBltMode( hSaveDc, HALFTONE );
 8855              } else {
 8856                  SetStretchBltMode( hSaveDc, COLORONCOLOR );
 8857              }
 8858  #endif
 8859  			StretchBlt( hSaveDc,
 8860                          0, 0,
 8861                          copyWidth, copyHeight,
 8862                          hdcScreen,
 8863                          monInfo.rcMonitor.left + copyX,
 8864                          monInfo.rcMonitor.top + copyY,
 8865                          copyWidth, copyHeight,
 8866                          SRCCOPY|CAPTUREBLT );
 8867  
 8868              if( OpenClipboard( hWnd )) {
 8869  
 8870                  EmptyClipboard();
 8871                  SetClipboardData( CF_BITMAP, hSaveBitmap );
 8872                  CloseClipboard();
 8873              }
 8874  
 8875              DeleteDC( hSaveDc );
 8876              }
 8877              break;
 8878  
 8879          case IDC_DRAW:
 8880              PostMessage( hWnd, WM_HOTKEY, DRAW_HOTKEY, 1 );
 8881              break;
 8882  
 8883          case IDC_ZOOM:
 8884              PostMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 1 );
 8885              break;
 8886  
 8887          case IDC_RECORD:
 8888              PostMessage( hWnd, WM_HOTKEY, RECORD_HOTKEY, 1 );
 8889              break;
 8890  
 8891          case IDC_OPTIONS:
 8892              // Don't show win32 forms options if started by PowerToys.
 8893              // Show the PowerToys Settings application instead.
 8894  
 8895              if( g_StartedByPowerToys )
 8896              {
 8897  #ifdef __ZOOMIT_POWERTOYS__
 8898                  OpenPowerToysSettingsApp();
 8899  #endif // __ZOOMIT_POWERTOYS__
 8900              }
 8901              else
 8902              {
 8903                  DialogBox( g_hInstance, L"OPTIONS", hWnd, OptionsProc );
 8904              }
 8905              break;
 8906  
 8907          case IDC_BREAK:
 8908          {
 8909              // Manage handles, clean visual transitions, and Options delta
 8910              if( g_TimerActive )
 8911              {
 8912                  if( activeBreakShowBackgroundFile != g_BreakShowBackgroundFile ||
 8913                      activeBreakShowDesktop != g_BreakShowDesktop )
 8914                  {
 8915                      if( g_BreakShowBackgroundFile && !g_BreakShowDesktop )
 8916                      {
 8917                          SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_DESTROY );
 8918                      }
 8919                      else
 8920                      {
 8921                          SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 );
 8922                      }
 8923                  }
 8924                  else
 8925                  {
 8926                      SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_DESTROY );
 8927                      g_TimerActive = TRUE;
 8928                  }
 8929              }
 8930  
 8931              hdcScreen = CreateDC( L"DISPLAY", static_cast<PTCHAR>(NULL), static_cast<PTCHAR>(NULL), static_cast<CONST DEVMODE*>(NULL) );
 8932  
 8933              // toggle second monitor
 8934              // FIX: we should save whether or not we've switched to a second monitor
 8935              // rather than just assume that the setting hasn't changed since the break timer
 8936              // became active
 8937              if( g_BreakOnSecondary )
 8938              {
 8939                  EnableDisableSecondaryDisplay( hWnd, TRUE, &secondaryDevMode );
 8940              }
 8941  
 8942              // Determine what monitor we're on
 8943              GetCursorPos( &cursorPos );
 8944              UpdateMonitorInfo( cursorPos, &monInfo );
 8945              width = monInfo.rcMonitor.right - monInfo.rcMonitor.left;
 8946              height = monInfo.rcMonitor.bottom - monInfo.rcMonitor.top;
 8947  
 8948              // Trigger desktop recapture as necessary when switching monitors
 8949              if( g_TimerActive && g_BreakShowDesktop && lastMonInfo.rcMonitor != monInfo.rcMonitor )
 8950              {
 8951                  lastMonInfo = monInfo;
 8952                  SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 );
 8953                  PostMessage( hWnd, WM_COMMAND, IDC_BREAK, 0 );
 8954                  break;
 8955              }
 8956              lastMonInfo = monInfo;
 8957  
 8958              // If the background is a file that hasn't been collected, grab it now
 8959              if( g_BreakShowBackgroundFile && !g_BreakShowDesktop &&
 8960                  ( !g_TimerActive || wcscmp( activeBreakBackgroundFile, g_BreakBackgroundFile ) ) )
 8961              {
 8962                  _tcscpy( activeBreakBackgroundFile, g_BreakBackgroundFile );
 8963  
 8964                  DeleteObject( g_hBackgroundBmp );
 8965                  DeleteDC( g_hDcBackgroundFile );
 8966  
 8967                  g_hBackgroundBmp = NULL;
 8968                  g_hBackgroundBmp = LoadImageFile( g_BreakBackgroundFile );
 8969                  if( g_hBackgroundBmp == NULL )
 8970                  {
 8971                      // Clean up hanging handles
 8972                      SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 );
 8973                      ErrorDialog( hWnd, L"Error loading background bitmap", GetLastError() );
 8974                      break;
 8975                  }
 8976                  g_hDcBackgroundFile = CreateCompatibleDC( hdcScreen );
 8977                  SelectObject( g_hDcBackgroundFile, g_hBackgroundBmp );
 8978              }
 8979              // If the background is a desktop that hasn't been collected, grab it now
 8980              else if( g_BreakShowBackgroundFile && g_BreakShowDesktop && !g_TimerActive )
 8981              {
 8982                  g_hBackgroundBmp = CreateFadedDesktopBackground( GetDC(NULL), & monInfo.rcMonitor, NULL );
 8983                  g_hDcBackgroundFile = CreateCompatibleDC( hdcScreen );
 8984                  SelectObject( g_hDcBackgroundFile, g_hBackgroundBmp );
 8985              }
 8986  
 8987              // Track Options.Break delta
 8988              activeBreakShowBackgroundFile = g_BreakShowBackgroundFile;
 8989              activeBreakShowDesktop = g_BreakShowDesktop;
 8990  
 8991              g_TimerActive = TRUE;
 8992  #ifdef __ZOOMIT_POWERTOYS__
 8993              if( g_StartedByPowerToys )
 8994              {
 8995                  Trace::ZoomItActivateBreak();
 8996              }
 8997  #endif // __ZOOMIT_POWERTOYS__
 8998  
 8999              breakTimeout = g_BreakTimeout * 60 + 1;
 9000  
 9001              // Create font
 9002              g_LogFont.lfHeight = height / 5;
 9003              hTimerFont = CreateFontIndirect( &g_LogFont );
 9004              g_LogFont.lfHeight = height / 8;
 9005              hNegativeTimerFont = CreateFontIndirect( &g_LogFont );
 9006  
 9007              // Create backing bitmap
 9008              hdcScreenCompat = CreateCompatibleDC(hdcScreen);
 9009              bmp.bmBitsPixel = static_cast<BYTE>(GetDeviceCaps(hdcScreen, BITSPIXEL));
 9010              bmp.bmPlanes = static_cast<BYTE>(GetDeviceCaps(hdcScreen, PLANES));
 9011              bmp.bmWidth = width;
 9012              bmp.bmHeight = height;
 9013              bmp.bmWidthBytes = ((bmp.bmWidth + 15) &~15)/8;
 9014              hbmpCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
 9015                  bmp.bmPlanes, bmp.bmBitsPixel, static_cast<CONST VOID *>(NULL));
 9016               SelectObject(hdcScreenCompat, hbmpCompat);
 9017  
 9018              SetTextColor( hdcScreenCompat, g_BreakPenColor );
 9019              SetBkMode( hdcScreenCompat, TRANSPARENT );
 9020              SelectObject( hdcScreenCompat, hTimerFont );
 9021  
 9022              EnableDisableOpacity( hWnd, TRUE );
 9023              EnableDisableScreenSaver( FALSE );
 9024  
 9025              SendMessage( hWnd, WM_TIMER, 0, 0 );
 9026              SetTimer( hWnd, 0, 1000, NULL );
 9027  
 9028              BringWindowToTop( hWnd );
 9029              SetForegroundWindow( hWnd );
 9030              SetActiveWindow( hWnd );
 9031              SetWindowPos( hWnd, HWND_NOTOPMOST, monInfo.rcMonitor.left, monInfo.rcMonitor.top,
 9032                      width, height, SWP_SHOWWINDOW );
 9033          }
 9034          break;
 9035  
 9036          case IDCANCEL:
 9037  
 9038              memset( &tNotifyIconData, 0, sizeof(tNotifyIconData));
 9039              tNotifyIconData.cbSize = sizeof(NOTIFYICONDATA);
 9040              tNotifyIconData.hWnd = hWnd;
 9041              tNotifyIconData.uID = 1;
 9042              Shell_NotifyIcon(NIM_DELETE, &tNotifyIconData);
 9043              reg.WriteRegSettings( RegSettings );
 9044  
 9045              if( hWndOptions )
 9046              {
 9047                  DestroyWindow( hWndOptions );
 9048              }
 9049              DestroyWindow( hWnd );
 9050              break;
 9051          }
 9052          break;
 9053  
 9054      case WM_TIMER:
 9055          switch( wParam ) {
 9056          case 0:
 9057              //
 9058              // Break timer
 9059              //
 9060              breakTimeout -= 1;
 9061              InvalidateRect( hWnd, NULL, FALSE );
 9062              if( breakTimeout == 0 && g_BreakPlaySoundFile ) {
 9063  
 9064                  PlaySound( g_BreakSoundFile, NULL, SND_FILENAME|SND_ASYNC );
 9065              }
 9066              break;
 9067  
 9068          case 2:
 9069          case 1:
 9070              doTelescopingZoomTimer();
 9071              break;
 9072  
 9073          case 3:
 9074              POINT mousePos;
 9075              GetCursorPos(&mousePos);
 9076              if (mousePos.x != cursorPos.x || mousePos.y != cursorPos.y)
 9077              {
 9078                  MONITORINFO monitorInfo = { sizeof(MONITORINFO) };
 9079                  UpdateMonitorInfo(mousePos, &monitorInfo);
 9080  
 9081                  mousePos.x -= monitorInfo.rcMonitor.left;
 9082                  mousePos.y -= monitorInfo.rcMonitor.top;
 9083  
 9084                  OutputDebug(L"RETRACKING MOUSE: x: %d y: %d\n", mousePos.x, mousePos.y);
 9085                  SendMessage(hWnd, WM_MOUSEMOVE, 0, MAKELPARAM(mousePos.x, mousePos.y));
 9086              }
 9087              break;
 9088          }
 9089          break;
 9090  
 9091      case WM_PAINT:
 9092  
 9093          hDc = BeginPaint(hWnd, &ps);
 9094  
 9095          if( ( ( g_RecordCropping == FALSE ) || ( zoomLevel == 1 ) ) && g_Zoomed ) {
 9096  
 9097              OutputDebug( L"PAINT x: %d y: %d width: %d height: %d zoomLevel: %g\n",
 9098                      cursorPos.x, cursorPos.y, width, height, zoomLevel );
 9099              GetZoomedTopLeftCoordinates( zoomLevel, &cursorPos, &x, width, &y, height );
 9100  #if SCALE_GDIPLUS
 9101              if ( zoomLevel >= zoomTelescopeTarget )  {
 9102                  // do a high-quality render
 9103                  extern void ScaleImage( HDC hdcDst, float xDst, float yDst, float wDst, float hDst,
 9104                                          HBITMAP bmSrc, float xSrc, float ySrc, float wSrc, float hSrc );
 9105  
 9106                  ScaleImage( ps.hdc,
 9107                              0, 0,
 9108                              (float)bmp.bmWidth, (float)bmp.bmHeight,
 9109                              hbmpCompat,
 9110                              (float)x, (float)y,
 9111                              width/zoomLevel, height/zoomLevel );
 9112              } else {
 9113                  // do a fast, less accurate render (but use smooth if enabled)
 9114                  SetStretchBltMode( hDc, g_SmoothImage ? HALFTONE : COLORONCOLOR );
 9115                  StretchBlt( ps.hdc,
 9116                          0, 0,
 9117                          bmp.bmWidth, bmp.bmHeight,
 9118                          hdcScreenCompat,
 9119                          x, y,
 9120                          (int) (width/zoomLevel), (int) (height/zoomLevel),
 9121                          SRCCOPY);
 9122              }
 9123  #else
 9124  #if SCALE_HALFTONE
 9125              SetStretchBltMode( hDc, zoomLevel == zoomTelescopeTarget ? HALFTONE : COLORONCOLOR );
 9126  #else
 9127              // Use HALFTONE for better quality when smooth image is enabled
 9128              if (g_SmoothImage) {
 9129                  SetStretchBltMode( hDc, HALFTONE );
 9130              } else {
 9131                  SetStretchBltMode( hDc, COLORONCOLOR );
 9132              }
 9133  #endif
 9134              StretchBlt( ps.hdc,
 9135                      0, 0,
 9136                      bmp.bmWidth, bmp.bmHeight,
 9137                      hdcScreenCompat,
 9138                      x, y,
 9139                      static_cast<int>(width/zoomLevel), static_cast<int>(height/zoomLevel),
 9140                      SRCCOPY|CAPTUREBLT );
 9141  #endif
 9142          } else if( g_TimerActive ) {
 9143  
 9144              // Fill bitmap with white
 9145              rc.top = rc.left = 0;
 9146              rc.bottom = height;
 9147              rc.right = width;
 9148              FillRect( hdcScreenCompat, &rc, GetSysColorBrush( COLOR_WINDOW ));
 9149  
 9150              // If there's a background bitmap, draw it in the center
 9151              if( g_hBackgroundBmp ) {
 9152  
 9153                  BITMAP local_bmp;
 9154                  GetObject(g_hBackgroundBmp, sizeof(local_bmp), &local_bmp);
 9155                  SetStretchBltMode( hdcScreenCompat, g_SmoothImage ? HALFTONE : COLORONCOLOR );
 9156                  if( g_BreakBackgroundStretch ) {
 9157                      StretchBlt( hdcScreenCompat, 0, 0, width, height,
 9158                          g_hDcBackgroundFile, 0, 0, local_bmp.bmWidth, local_bmp.bmHeight, SRCCOPY|CAPTUREBLT  );
 9159                  } else {
 9160                      BitBlt( hdcScreenCompat, width/2 - local_bmp.bmWidth/2, height/2 - local_bmp.bmHeight/2,
 9161                          local_bmp.bmWidth, local_bmp.bmHeight, g_hDcBackgroundFile, 0, 0, SRCCOPY|CAPTUREBLT  );
 9162                  }
 9163              }
 9164  
 9165              // Draw time
 9166              if( breakTimeout > 0 ) {
 9167  
 9168                  _stprintf( timerText, L"% 2d:%02d", breakTimeout/60, breakTimeout % 60 );
 9169  
 9170              } else {
 9171  
 9172                  _tcscpy( timerText, L"0:00" );
 9173              }
 9174              rc.left = rc.top = 0;
 9175              DrawText( hdcScreenCompat, timerText, -1, &rc,
 9176                  DT_NOCLIP|DT_LEFT|DT_NOPREFIX|DT_CALCRECT );
 9177  
 9178              rc1.left = rc1.right = rc1.bottom = rc1.top = 0;
 9179              if( g_ShowExpiredTime && breakTimeout < 0 ) {
 9180  
 9181                  _stprintf( negativeTimerText, L"(-% 2d:%02d)",
 9182                          -breakTimeout/60, -breakTimeout % 60 );
 9183                  HFONT prevFont = static_cast<HFONT>(SelectObject( hdcScreenCompat, hNegativeTimerFont ));
 9184                  DrawText( hdcScreenCompat, negativeTimerText, -1, &rc1,
 9185                      DT_NOCLIP|DT_LEFT|DT_NOPREFIX|DT_CALCRECT );
 9186                  SelectObject( hdcScreenCompat, prevFont );
 9187              }
 9188  
 9189              // Position time vertically
 9190              switch( g_BreakTimerPosition ) {
 9191              case 0:
 9192              case 1:
 9193              case 2:
 9194                  rc.top = 50;
 9195                  break;
 9196              case 3:
 9197              case 4:
 9198              case 5:
 9199                  rc.top = (height - (rc.bottom - rc.top))/2;
 9200                  break;
 9201              case 6:
 9202              case 7:
 9203              case 8:
 9204                  rc.top = height - rc.bottom - 50 - rc1.bottom;
 9205                  break;
 9206              }
 9207  
 9208              // Position time horizontally
 9209              switch( g_BreakTimerPosition ) {
 9210              case 0:
 9211              case 3:
 9212              case 6:
 9213                  rc.left = 50;
 9214                  break;
 9215              case 1:
 9216              case 4:
 9217              case 7:
 9218                  rc.left = (width - (rc.right - rc.left))/2;
 9219                  break;
 9220              case 2:
 9221              case 5:
 9222              case 8:
 9223                  rc.left = width - rc.right - 50;
 9224                  break;
 9225              }
 9226              rc.bottom += rc.top;
 9227              rc.right += rc.left;
 9228  
 9229              DrawText( hdcScreenCompat, timerText, -1, &rc, DT_NOCLIP|DT_LEFT|DT_NOPREFIX );
 9230  
 9231              if( g_ShowExpiredTime && breakTimeout < 0 ) {
 9232  
 9233                  rc1.top = rc.bottom + 10;
 9234                  rc1.left = rc.left + ((rc.right - rc.left)-(rc1.right-rc1.left))/2;
 9235                  HFONT prevFont = static_cast<HFONT>(SelectObject( hdcScreenCompat, hNegativeTimerFont ));
 9236                  DrawText( hdcScreenCompat, negativeTimerText, -1, &rc1,
 9237                      DT_NOCLIP|DT_LEFT|DT_NOPREFIX );
 9238                  SelectObject( hdcScreenCompat, prevFont );
 9239              }
 9240  
 9241              // Copy to screen
 9242              BitBlt( ps.hdc, 0, 0, width, height, hdcScreenCompat, 0, 0, SRCCOPY|CAPTUREBLT  );
 9243          }
 9244          EndPaint(hWnd, &ps);
 9245          return TRUE;
 9246  
 9247      case WM_DESTROY:
 9248          CleanupDarkModeResources();
 9249          PostQuitMessage( 0 );
 9250          break;
 9251  
 9252      default:
 9253          if( message == wmTaskbarCreated )
 9254          {
 9255              if( g_ShowTrayIcon )
 9256              {
 9257                  EnableDisableTrayIcon( hWnd, TRUE );
 9258              }
 9259              return TRUE;
 9260          }
 9261         return DefWindowProc(hWnd, message, wParam, lParam );
 9262      }
 9263      return 0;
 9264  }
 9265  
 9266  
 9267  //----------------------------------------------------------------------------
 9268  //
 9269  // LiveZoomWndProc
 9270  //
 9271  //----------------------------------------------------------------------------
 9272  LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
 9273  {
 9274      RECT		rc;
 9275      POINT		cursorPos;
 9276      static int	width, height;
 9277      static MONITORINFO	monInfo;
 9278      HDC			hdcScreen;
 9279  #if 0
 9280      int			delta;
 9281      BOOLEAN		zoomIn;
 9282  #endif
 9283      static POINT	lastCursorPos;
 9284      POINT			adjustedCursorPos, zoomCenterPos;
 9285      int				moveWidth, moveHeight;
 9286      int				sourceRectHeight, sourceRectWidth;
 9287      DWORD			curTickCount;
 9288      RECT			sourceRect{};
 9289      static RECT		lastSourceRect;
 9290      static float	zoomLevel;
 9291      static float	zoomTelescopeStep;
 9292      static float	zoomTelescopeTarget;
 9293      static DWORD	prevZoomStepTickCount = 0;
 9294      static BOOL		dwmEnabled = FALSE;
 9295      static BOOLEAN	startedInPresentationMode = FALSE;
 9296      MAGTRANSFORM matrix;
 9297  
 9298      switch (message)  {
 9299      case WM_CREATE:
 9300  
 9301          // Initialize
 9302          pMagInitialize();
 9303          if (pDwmIsCompositionEnabled) pDwmIsCompositionEnabled(&dwmEnabled);
 9304  
 9305          // Create the zoom window
 9306          if( !g_fullScreenWorkaround ) {
 9307  
 9308              g_hWndLiveZoomMag = CreateWindowEx( 0,
 9309                                          WC_MAGNIFIER,
 9310                                          TEXT("MagnifierWindow"),
 9311                                          WS_CHILD | MS_SHOWMAGNIFIEDCURSOR | WS_VISIBLE,
 9312                                          0, 0, 0, 0, hWnd, NULL, g_hInstance, NULL );
 9313          }
 9314          ShowWindow( hWnd, SW_SHOW );
 9315          InvalidateRect( g_hWndLiveZoomMag, NULL, TRUE );
 9316  
 9317          if( !g_fullScreenWorkaround )
 9318              SetForegroundWindow(static_cast<HWND>(reinterpret_cast<LPCREATESTRUCT>(lParam)->lpCreateParams));
 9319  
 9320          // If we're not on Win7+, then set a timer to go off two hours from
 9321          // now
 9322          if( g_OsVersion < WIN7_VERSION ) {
 9323  
 9324              startedInPresentationMode = IsPresentationMode();
 9325              // if we're not in presentation mode, kill ourselves after a timeout
 9326              if( !startedInPresentationMode ) {
 9327  
 9328                  SetTimer( hWnd, 1, LIVEZOOM_WINDOW_TIMEOUT, NULL );
 9329              }
 9330          }
 9331          break;
 9332  
 9333      case WM_SHOWWINDOW:
 9334          if( wParam == TRUE ) {
 9335  
 9336              // Determine what monitor we're on
 9337              lastCursorPos.x = -1;
 9338              hdcScreen	= GetDC( NULL );
 9339              GetCursorPos( &cursorPos );
 9340              UpdateMonitorInfo( cursorPos, &monInfo );
 9341              width = monInfo.rcMonitor.right - monInfo.rcMonitor.left;
 9342              height = monInfo.rcMonitor.bottom - monInfo.rcMonitor.top;
 9343              lastSourceRect.left = lastSourceRect.right = 0;
 9344              lastSourceRect.right = width;
 9345              lastSourceRect.bottom = height;
 9346  
 9347              // Set window size
 9348              if( !g_fullScreenWorkaround ) {
 9349  
 9350                  SetWindowPos( hWnd, NULL, monInfo.rcMonitor.left, monInfo.rcMonitor.top,
 9351                      monInfo.rcMonitor.right - monInfo.rcMonitor.left,
 9352                      monInfo.rcMonitor.bottom - monInfo.rcMonitor.top,
 9353                      SWP_NOACTIVATE | SWP_NOZORDER );
 9354                  UpdateWindow(hWnd);
 9355              }
 9356  
 9357              // Are we coming back from a static zoom that
 9358              // was started while we were live zoomed?
 9359              if( g_ZoomOnLiveZoom ) {
 9360  
 9361                  // Force a zoom to 2x without telescope
 9362                  prevZoomStepTickCount = 0;
 9363                  zoomLevel = static_cast<float>(1.9);
 9364                  zoomTelescopeTarget = 2.0;
 9365                  zoomTelescopeStep = 2.0;
 9366  
 9367              } else {
 9368  
 9369                  zoomTelescopeStep = ZOOM_LEVEL_STEP_IN;
 9370                  zoomTelescopeTarget = g_ZoomLevels[g_SliderZoomLevel];
 9371  
 9372                  prevZoomStepTickCount = 0;
 9373                  if( dwmEnabled ) {
 9374  
 9375                      zoomLevel = static_cast<float>(1);
 9376  
 9377                  } else {
 9378  
 9379                      zoomLevel = static_cast<float>(1.9);
 9380                  }
 9381              }
 9382              RegisterHotKey( hWnd, 0, MOD_CONTROL, VK_UP );
 9383              RegisterHotKey( hWnd, 1, MOD_CONTROL, VK_DOWN );
 9384  
 9385              // Hide hardware cursor
 9386              if( !g_fullScreenWorkaround )
 9387                  if( pMagShowSystemCursor ) pMagShowSystemCursor( FALSE );
 9388  
 9389              if( g_RecordToggle )
 9390                  g_RecordingSession->EnableCursorCapture( false );
 9391  
 9392              GetCursorPos( &lastCursorPos );
 9393              SetCursorPos( lastCursorPos.x, lastCursorPos.y );
 9394  
 9395              SendMessage( hWnd, WM_TIMER, 0, 0);
 9396              SetTimer( hWnd, 0, ZOOM_LEVEL_STEP_TIME, NULL );
 9397  
 9398          } else {
 9399  
 9400              KillTimer( hWnd, 0 );
 9401  
 9402              if( g_RecordToggle )
 9403                  g_RecordingSession->EnableCursorCapture();
 9404  
 9405              if( !g_fullScreenWorkaround )
 9406                  if( pMagShowSystemCursor ) pMagShowSystemCursor( TRUE );
 9407  
 9408              // Reset the timer to expire two hours from now
 9409              if( g_OsVersion < WIN7_VERSION && !IsPresentationMode()) {
 9410  
 9411                  KillTimer( hWnd, 1 );
 9412                  SetTimer( hWnd, 1, LIVEZOOM_WINDOW_TIMEOUT, NULL );
 9413              } else {
 9414  
 9415                  DestroyWindow( hWnd );
 9416              }
 9417              UnregisterHotKey( hWnd, 0 );
 9418              UnregisterHotKey( hWnd, 1 );
 9419          }
 9420          break;
 9421  
 9422      case WM_TIMER:
 9423          switch( wParam ) {
 9424          case 0: {
 9425              // if we're cropping, do not move
 9426              if( g_RecordCropping == TRUE )
 9427              {
 9428                  // Still redraw to keep the contents live
 9429                  InvalidateRect( g_hWndLiveZoomMag, nullptr, TRUE );
 9430                  break;
 9431              }
 9432  
 9433              GetCursorPos(&cursorPos);
 9434  
 9435              // Reclaim topmost status, to prevent unmagnified menus from remaining in view.
 9436              memset(&matrix, 0, sizeof(matrix));
 9437              if( !g_fullScreenWorkaround ) {
 9438  
 9439                  pSetLayeredWindowAttributes( hWnd, 0, 255, LWA_ALPHA );
 9440                  SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0,
 9441                      SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
 9442  
 9443                  OutputDebug(L"LIVEZOOM RECLAIM\n");
 9444              }
 9445  
 9446              sourceRectWidth = lastSourceRect.right - lastSourceRect.left;
 9447              sourceRectHeight = lastSourceRect.bottom - lastSourceRect.top;
 9448              moveWidth = sourceRectWidth/LIVEZOOM_MOVE_REGIONS;
 9449              moveHeight = sourceRectHeight/LIVEZOOM_MOVE_REGIONS;
 9450              curTickCount = GetTickCount();
 9451              if( zoomLevel != zoomTelescopeTarget &&
 9452                  (prevZoomStepTickCount == 0 || (curTickCount - prevZoomStepTickCount > ZOOM_LEVEL_STEP_TIME)) ) {
 9453  
 9454                  prevZoomStepTickCount = curTickCount;
 9455                  if( (zoomTelescopeStep > 1 && zoomLevel*zoomTelescopeStep >= zoomTelescopeTarget ) ||
 9456                      (zoomTelescopeStep < 1 && zoomLevel*zoomTelescopeStep <= zoomTelescopeTarget )) {
 9457  
 9458                      zoomLevel = zoomTelescopeTarget;
 9459  
 9460                  } else {
 9461  
 9462                      zoomLevel *= zoomTelescopeStep;
 9463                  }
 9464                  // Time to exit zoom mode?
 9465                  if( zoomTelescopeTarget == 1 && zoomLevel == 1 ) {
 9466  
 9467  #if WINDOWS_CURSOR_RECORDING_WORKAROUND
 9468                      if( g_RecordToggle )
 9469                          g_LiveZoomLevelOne = true;
 9470                      else
 9471  #endif
 9472                      ShowWindow( hWnd, SW_HIDE );
 9473  
 9474                  } else {
 9475  
 9476                      matrix.v[0][0] = zoomLevel;
 9477                      matrix.v[0][2] = (static_cast<float>(-lastSourceRect.left) * zoomLevel);
 9478                      matrix.v[1][1] = zoomLevel;
 9479                      matrix.v[1][2] = (static_cast<float>(-lastSourceRect.top) * zoomLevel );
 9480                      matrix.v[2][2] = 1.0f;
 9481                  }
 9482  
 9483                  //
 9484                  // Pre-adjust for monitor boundary
 9485                  //
 9486                  adjustedCursorPos.x = cursorPos.x - monInfo.rcMonitor.left;
 9487                  adjustedCursorPos.y = cursorPos.y - monInfo.rcMonitor.top;
 9488                  GetZoomedTopLeftCoordinates( zoomLevel, &adjustedCursorPos, reinterpret_cast<int *>(&zoomCenterPos.x), width,
 9489                                  reinterpret_cast<int *>(&zoomCenterPos.y), height );
 9490  
 9491                  //
 9492                  // Add back monitor boundary
 9493                  //
 9494                  zoomCenterPos.x += monInfo.rcMonitor.left + static_cast<LONG>(width/zoomLevel/2);
 9495                  zoomCenterPos.y += monInfo.rcMonitor.top + static_cast<LONG>(height/zoomLevel/2);
 9496  
 9497              } else {
 9498  
 9499                  int xOffset = cursorPos.x - lastSourceRect.left;
 9500                  int yOffset = cursorPos.y - lastSourceRect.top;
 9501                  zoomCenterPos.x = 0;
 9502                  zoomCenterPos.y = 0;
 9503                  if( xOffset < moveWidth )
 9504                      zoomCenterPos.x = lastSourceRect.left + sourceRectWidth/2 - (moveWidth - xOffset);
 9505                  else if( xOffset > moveWidth * (LIVEZOOM_MOVE_REGIONS-1) )
 9506                      zoomCenterPos.x = lastSourceRect.left + sourceRectWidth/2 + (xOffset - moveWidth*(LIVEZOOM_MOVE_REGIONS-1));
 9507                  if( yOffset < moveHeight )
 9508                      zoomCenterPos.y = lastSourceRect.top + sourceRectHeight/2 - (moveHeight - yOffset);
 9509                  else if( yOffset > moveHeight * (LIVEZOOM_MOVE_REGIONS-1) )
 9510                      zoomCenterPos.y = lastSourceRect.top + sourceRectHeight/2 + (yOffset - moveHeight*(LIVEZOOM_MOVE_REGIONS-1));
 9511              }
 9512              if( matrix.v[0][0] || zoomCenterPos.x || zoomCenterPos.y ) {
 9513  
 9514                  if( zoomCenterPos.y == 0 )
 9515                      zoomCenterPos.y = lastSourceRect.top + sourceRectHeight/2;
 9516                  if( zoomCenterPos.x == 0 )
 9517                      zoomCenterPos.x = lastSourceRect.left + sourceRectWidth/2;
 9518  
 9519                  int zoomWidth = static_cast<int>(width / zoomLevel);
 9520                  int zoomHeight = static_cast<int>(height/ zoomLevel);
 9521                  sourceRect.left = zoomCenterPos.x - zoomWidth / 2;
 9522                  sourceRect.top = zoomCenterPos.y -  zoomHeight / 2;
 9523  
 9524                  // Don't scroll outside desktop area.
 9525                  if (sourceRect.left < monInfo.rcMonitor.left)
 9526                      sourceRect.left = monInfo.rcMonitor.left;
 9527                  else if (sourceRect.left > monInfo.rcMonitor.right - zoomWidth )
 9528                      sourceRect.left = monInfo.rcMonitor.right - zoomWidth;
 9529                  sourceRect.right = sourceRect.left + zoomWidth;
 9530                  if (sourceRect.top < monInfo.rcMonitor.top)
 9531                      sourceRect.top = monInfo.rcMonitor.top;
 9532                  else if (sourceRect.top > monInfo.rcMonitor.bottom - zoomHeight)
 9533                      sourceRect.top = monInfo.rcMonitor.bottom - zoomHeight;
 9534                  sourceRect.bottom = sourceRect.top + zoomHeight;
 9535  
 9536                  if( g_ZoomOnLiveZoom ) {
 9537  
 9538                      matrix.v[0][0] = static_cast<float>(1.0);
 9539                      matrix.v[0][2] = (static_cast<float>(-monInfo.rcMonitor.left));
 9540  
 9541                      matrix.v[1][1] = static_cast<float>(1.0);
 9542                      matrix.v[1][2] = (static_cast<float>(-monInfo.rcMonitor.top));
 9543  
 9544                      matrix.v[2][2] = 1.0f;
 9545  
 9546                  } else if( lastSourceRect.left != sourceRect.left ||
 9547                      lastSourceRect.top  != sourceRect.top ) {
 9548  
 9549                      matrix.v[0][0] = zoomLevel;
 9550                      matrix.v[0][2] = (static_cast<float>(-sourceRect.left) * zoomLevel);
 9551  
 9552                      matrix.v[1][1] = zoomLevel;
 9553                      matrix.v[1][2] = (static_cast<float>(-sourceRect.top) * zoomLevel);
 9554  
 9555                      matrix.v[2][2] = 1.0f;
 9556                  }
 9557                  lastSourceRect = sourceRect;
 9558              }
 9559              lastCursorPos = cursorPos;
 9560  
 9561              // Update source and zoom if necessary
 9562              if( matrix.v[0][0] ) {
 9563  
 9564                  OutputDebug(L"LIVEZOOM update\n");
 9565                  if( g_fullScreenWorkaround ) {
 9566  
 9567                      pMagSetFullscreenTransform(zoomLevel, sourceRect.left, sourceRect.top);
 9568                      pMagSetInputTransform(TRUE, &sourceRect, &monInfo.rcMonitor);
 9569                  }
 9570                  else {
 9571  
 9572                      pMagSetWindowTransform(g_hWndLiveZoomMag, &matrix);
 9573                  }
 9574              }
 9575  
 9576              if( !g_fullScreenWorkaround ) {
 9577  
 9578                  // Force redraw to refresh screen contents
 9579                  InvalidateRect(g_hWndLiveZoomMag, NULL, TRUE);
 9580              }
 9581  
 9582              // are we done zooming?
 9583              if( zoomLevel == 1 ) {
 9584  
 9585  #if WINDOWS_CURSOR_RECORDING_WORKAROUND
 9586                  if( g_RecordToggle ) {
 9587  
 9588                      g_LiveZoomLevelOne = true;
 9589                  }
 9590                  else {
 9591  
 9592  #endif
 9593                  if( g_OsVersion < WIN7_VERSION ) {
 9594  
 9595                      ShowWindow( hWnd, SW_HIDE );
 9596  
 9597                  } else {
 9598  
 9599                      DestroyWindow( hWnd );
 9600                  }
 9601              }
 9602  #if WINDOWS_CURSOR_RECORDING_WORKAROUND
 9603              }
 9604  #endif
 9605              }
 9606              break;
 9607          case 1: {
 9608  
 9609              if( !IsWindowVisible( hWnd )) {
 9610  
 9611                  // This is the cached window timeout. If not in presentation mode,
 9612                  // time to exit
 9613                  if( !IsPresentationMode()) {
 9614  
 9615                      DestroyWindow( hWnd );
 9616                  }
 9617              }
 9618              }
 9619              break;
 9620          }
 9621          break;
 9622  
 9623      case WM_SETTINGCHANGE:
 9624          if( g_OsVersion < WIN7_VERSION ) {
 9625  
 9626              if( startedInPresentationMode && !IsPresentationMode()) {
 9627  
 9628                  // Existing presentation mode
 9629                  DestroyWindow( hWnd );
 9630  
 9631              } else if( !startedInPresentationMode && IsPresentationMode()) {
 9632  
 9633                  // Kill the timer if one was configured, because now
 9634                  // we're going to go away when they exit presentation mode
 9635                  KillTimer( hWnd, 1 );
 9636              }
 9637          }
 9638          break;
 9639  
 9640      case WM_HOTKEY: {
 9641          float newZoomLevel = zoomLevel;
 9642          switch( wParam ) {
 9643          case 0:
 9644              // zoom in
 9645              if( newZoomLevel < ZOOM_LEVEL_MAX )
 9646                  newZoomLevel *= 2;
 9647              zoomTelescopeStep = ZOOM_LEVEL_STEP_IN;
 9648              break;
 9649  
 9650          case 1:
 9651              if( newZoomLevel > 2 )
 9652                  newZoomLevel /= 2;
 9653              else {
 9654  
 9655                  newZoomLevel *= .75;
 9656                  if( newZoomLevel < ZOOM_LEVEL_MIN )
 9657                      newZoomLevel = ZOOM_LEVEL_MIN;
 9658              }
 9659              zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT;
 9660              break;
 9661          }
 9662          zoomTelescopeTarget = newZoomLevel;
 9663          if( !dwmEnabled ) {
 9664  
 9665              zoomLevel = newZoomLevel;
 9666          }
 9667          }
 9668          break;
 9669  
 9670      // NOTE: keyboard and mouse input actually don't get sent to us at all when in live zoom mode
 9671      case WM_KEYDOWN:
 9672          switch( wParam ) {
 9673          case VK_ESCAPE:
 9674              zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT;
 9675              zoomTelescopeTarget = 1.0;
 9676              if( !dwmEnabled ) {
 9677  
 9678                  zoomLevel = static_cast<float>(1.1);
 9679              }
 9680              break;
 9681  
 9682          case VK_UP:
 9683              SendMessage( hWnd, WM_MOUSEWHEEL,
 9684                  MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 ? MK_CONTROL: 0, WHEEL_DELTA), 0 );
 9685              return TRUE;
 9686  
 9687          case VK_DOWN:
 9688              SendMessage( hWnd, WM_MOUSEWHEEL,
 9689                  MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 ? MK_CONTROL: 0, -WHEEL_DELTA), 0 );
 9690              return TRUE;
 9691          }
 9692          break;
 9693      case WM_DESTROY:
 9694          g_hWndLiveZoom = NULL;
 9695          break;
 9696  
 9697      case WM_SIZE:
 9698          GetClientRect(hWnd, &rc);
 9699          SetWindowPos(g_hWndLiveZoomMag, NULL,
 9700              rc.left, rc.top, rc.right, rc.bottom, 0 );
 9701          break;
 9702  
 9703      case WM_USER_GET_ZOOM_LEVEL:
 9704          return reinterpret_cast<LRESULT>(&zoomLevel);
 9705  
 9706      case WM_USER_GET_SOURCE_RECT:
 9707          return reinterpret_cast<LRESULT>(&lastSourceRect);
 9708  
 9709      case WM_USER_MAGNIFY_CURSOR:
 9710          {
 9711              auto style = GetWindowLong( g_hWndLiveZoomMag, GWL_STYLE );
 9712              if( wParam == TRUE )
 9713              {
 9714                  style |= MS_SHOWMAGNIFIEDCURSOR;
 9715              }
 9716              else
 9717              {
 9718                  style &= ~MS_SHOWMAGNIFIEDCURSOR;
 9719              }
 9720              SetWindowLong( g_hWndLiveZoomMag, GWL_STYLE, style );
 9721              InvalidateRect( g_hWndLiveZoomMag, nullptr, TRUE );
 9722              RedrawWindow( hWnd, nullptr, nullptr, RDW_ALLCHILDREN | RDW_UPDATENOW );
 9723          }
 9724          break;
 9725  
 9726      case WM_USER_SET_ZOOM:
 9727          {
 9728              if( g_RecordToggle )
 9729              {
 9730                  g_SelectRectangle.UpdateOwner( hWnd );
 9731              }
 9732  
 9733              if( lParam != NULL ) {
 9734  
 9735                  lastSourceRect = *reinterpret_cast<RECT *>(lParam);
 9736              }
 9737  #if WINDOWS_CURSOR_RECORDING_WORKAROUND
 9738              if( g_LiveZoomLevelOne ) {
 9739  
 9740                  g_LiveZoomLevelOne = FALSE;
 9741  
 9742                  zoomTelescopeTarget = static_cast<float>(wParam);
 9743                  zoomTelescopeStep = ZOOM_LEVEL_STEP_IN;
 9744                  prevZoomStepTickCount = 0;
 9745                  zoomLevel = 1.0;
 9746  
 9747                  break;
 9748              }
 9749  #endif
 9750              zoomLevel = static_cast<float>(wParam);
 9751              zoomTelescopeTarget = zoomLevel;
 9752              matrix.v[0][0] = zoomLevel;
 9753              matrix.v[0][2] = (static_cast<float>(-lastSourceRect.left) * static_cast<float>(wParam));
 9754  
 9755              matrix.v[1][1] = zoomLevel;
 9756              matrix.v[1][2] = (static_cast<float>(-lastSourceRect.top) * static_cast<float>(wParam));
 9757  
 9758              matrix.v[2][2] = 1.0f;
 9759  
 9760              if( g_fullScreenWorkaround ) {
 9761  
 9762                  pMagSetFullscreenTransform(zoomLevel, lastSourceRect.left, lastSourceRect.top);
 9763                  pMagSetInputTransform(TRUE, &lastSourceRect, &monInfo.rcMonitor);
 9764              }
 9765              else {
 9766  
 9767                  pMagSetWindowTransform(g_hWndLiveZoomMag, &matrix);
 9768              }
 9769          }
 9770          break;
 9771  
 9772      default:
 9773          return DefWindowProc(hWnd, message, wParam, lParam);
 9774      }
 9775      return 0;
 9776  }
 9777  
 9778  
 9779  //----------------------------------------------------------------------------
 9780  //
 9781  // Wrapper functions for explicit linking to d3d11.dll
 9782  //
 9783  //----------------------------------------------------------------------------
 9784  
 9785  HRESULT __stdcall WrapCreateDirect3D11DeviceFromDXGIDevice(
 9786      IDXGIDevice		*dxgiDevice,
 9787      IInspectable	**graphicsDevice)
 9788  {
 9789      if( pCreateDirect3D11DeviceFromDXGIDevice == nullptr )
 9790          return E_NOINTERFACE;
 9791  
 9792      return pCreateDirect3D11DeviceFromDXGIDevice( dxgiDevice, graphicsDevice );
 9793  }
 9794  
 9795  HRESULT __stdcall WrapCreateDirect3D11SurfaceFromDXGISurface(
 9796      IDXGISurface	*dxgiSurface,
 9797      IInspectable	**graphicsSurface)
 9798  {
 9799      if( pCreateDirect3D11SurfaceFromDXGISurface == nullptr )
 9800          return E_NOINTERFACE;
 9801  
 9802      return pCreateDirect3D11SurfaceFromDXGISurface( dxgiSurface, graphicsSurface );
 9803  }
 9804  
 9805  HRESULT __stdcall WrapD3D11CreateDevice(
 9806      IDXGIAdapter			*pAdapter,
 9807      D3D_DRIVER_TYPE			DriverType,
 9808      HMODULE					Software,
 9809      UINT					Flags,
 9810      const D3D_FEATURE_LEVEL	*pFeatureLevels,
 9811      UINT					FeatureLevels,
 9812      UINT					SDKVersion,
 9813      ID3D11Device			**ppDevice,
 9814      D3D_FEATURE_LEVEL		*pFeatureLevel,
 9815      ID3D11DeviceContext		**ppImmediateContext)
 9816  {
 9817      if( pD3D11CreateDevice == nullptr )
 9818          return E_NOINTERFACE;
 9819  
 9820      return pD3D11CreateDevice( pAdapter, DriverType, Software, Flags, pFeatureLevels,
 9821                  FeatureLevels, SDKVersion, ppDevice, pFeatureLevel, ppImmediateContext );
 9822  }
 9823  
 9824  
 9825  //----------------------------------------------------------------------------
 9826  //
 9827  // InitInstance
 9828  //
 9829  //----------------------------------------------------------------------------
 9830  HWND InitInstance( HINSTANCE hInstance, int nCmdShow )
 9831  {
 9832      WNDCLASS  wcZoomIt;
 9833      HWND	  hWndMain;
 9834  
 9835      g_hInstance = hInstance;
 9836  
 9837      // If magnification, set default hotkey for live zoom
 9838      if( pMagInitialize ) {
 9839  
 9840          // register live zoom host window
 9841          wcZoomIt.style          = CS_HREDRAW | CS_VREDRAW;
 9842          wcZoomIt.lpfnWndProc    = LiveZoomWndProc;
 9843          wcZoomIt.cbClsExtra     = 0;
 9844          wcZoomIt.cbWndExtra     = 0;
 9845          wcZoomIt.hInstance      = hInstance;
 9846          wcZoomIt.hIcon          = 0;
 9847          wcZoomIt.hCursor        = LoadCursor(NULL, IDC_ARROW);
 9848          wcZoomIt.hbrBackground  = NULL;
 9849          wcZoomIt.lpszMenuName   = NULL;
 9850          wcZoomIt.lpszClassName  = L"MagnifierClass";
 9851          RegisterClass(&wcZoomIt);
 9852  
 9853      } else {
 9854  
 9855          g_LiveZoomToggleKey = 0;
 9856      }
 9857  
 9858      wcZoomIt.style = 0;
 9859      wcZoomIt.lpfnWndProc	= (WNDPROC)MainWndProc;
 9860      wcZoomIt.cbClsExtra		= 0;
 9861      wcZoomIt.cbWndExtra		= 0;
 9862      wcZoomIt.hInstance		= hInstance;       wcZoomIt.hIcon			= NULL;
 9863      wcZoomIt.hCursor		= LoadCursor( hInstance, L"NULLCURSOR" );
 9864      wcZoomIt.hbrBackground	= NULL;
 9865      wcZoomIt.lpszMenuName	= NULL;
 9866      wcZoomIt.lpszClassName	= L"ZoomitClass";
 9867      if ( ! RegisterClass(&wcZoomIt) )
 9868          return FALSE;
 9869  
 9870      hWndMain = CreateWindowEx( WS_EX_TOOLWINDOW, L"ZoomitClass",
 9871                      L"Zoomit Zoom Window",
 9872                      WS_POPUP,
 9873                      0, 0,
 9874                      0, 0,
 9875                      NULL,
 9876                      NULL,
 9877                      hInstance,
 9878                      NULL);
 9879  
 9880      // If window could not be created, return "failure"
 9881      if (!hWndMain )
 9882          return NULL;
 9883  
 9884      // Make the window visible; update its client area; and return "success"
 9885      ShowWindow(hWndMain, SW_HIDE);
 9886  
 9887      // Add tray icon
 9888      EnableDisableTrayIcon( hWndMain, g_ShowTrayIcon );
 9889      return hWndMain;
 9890  
 9891  }
 9892  
 9893  // Dispatch commands coming from the PowerToys IPC channel.
 9894  #ifdef __ZOOMIT_POWERTOYS__
 9895  void ZoomIt_DispatchCommand(ZoomItCommand cmd)
 9896  {
 9897      auto post_hotkey = [](WPARAM id)
 9898      {
 9899          if (g_hWndMain != nullptr)
 9900          {
 9901              PostMessage(g_hWndMain, WM_HOTKEY, id, 0);
 9902          }
 9903      };
 9904  
 9905      switch (cmd)
 9906      {
 9907      case ZoomItCommand::Zoom:
 9908          if (g_hWndMain != nullptr)
 9909          {
 9910              PostMessage(g_hWndMain, WM_COMMAND, IDC_ZOOM, 0);
 9911          }
 9912          Trace::ZoomItActivateZoom();
 9913          break;
 9914      case ZoomItCommand::Draw:
 9915          post_hotkey(DRAW_HOTKEY);
 9916          Trace::ZoomItActivateDraw();
 9917          break;
 9918      case ZoomItCommand::Break:
 9919          post_hotkey(BREAK_HOTKEY);
 9920          Trace::ZoomItActivateBreak();
 9921          break;
 9922      case ZoomItCommand::LiveZoom:
 9923          post_hotkey(LIVE_HOTKEY);
 9924          Trace::ZoomItActivateLiveZoom();
 9925          break;
 9926      case ZoomItCommand::Snip:
 9927          post_hotkey(SNIP_HOTKEY);
 9928          Trace::ZoomItActivateSnip();
 9929          break;
 9930      case ZoomItCommand::Record:
 9931          post_hotkey(RECORD_HOTKEY);
 9932          Trace::ZoomItActivateRecord();
 9933          break;
 9934      default:
 9935          break;
 9936      }
 9937  }
 9938  #endif
 9939  
 9940  //----------------------------------------------------------------------------
 9941  //
 9942  // WinMain
 9943  //
 9944  //----------------------------------------------------------------------------
 9945  int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
 9946      _In_ PWSTR lpCmdLine, _In_ int nCmdShow )
 9947  {
 9948      MSG					msg;
 9949      HACCEL				hAccel;
 9950  
 9951      if( !ShowEula( APPNAME, NULL, NULL )) return 1;
 9952  
 9953  #ifdef __ZOOMIT_POWERTOYS__
 9954      if (powertoys_gpo::getConfiguredZoomItEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
 9955      {
 9956          Logger::warn(L"Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
 9957          return 1;
 9958      }
 9959  
 9960      Shared::Trace::ETWTrace* trace = nullptr;
 9961      std::wstring pid = std::wstring(lpCmdLine); // The PowerToys pid is the argument to the process.
 9962      auto mainThreadId = GetCurrentThreadId();
 9963      if (!pid.empty())
 9964      {
 9965          g_StartedByPowerToys = TRUE;
 9966  
 9967          trace = new Shared::Trace::ETWTrace();
 9968          Trace::RegisterProvider();
 9969          trace->UpdateState(true);
 9970          Trace::ZoomItStarted();
 9971  
 9972          // Initialize logger
 9973          LoggerHelpers::init_logger(L"ZoomIt", L"", LogSettings::zoomItLoggerName);
 9974          ProcessWaiter::OnProcessTerminate(pid, [mainThreadId](int err) {
 9975              if (err != ERROR_SUCCESS)
 9976              {
 9977                  Logger::error(L"Failed to wait for parent process exit. {}", get_last_error_or_default(err));
 9978              }
 9979              else
 9980              {
 9981                  Logger::trace(L"PowerToys runner exited.");
 9982              }
 9983  
 9984              Logger::trace(L"Exiting ZoomIt");
 9985              PostThreadMessage(mainThreadId, WM_QUIT, 0, 0);
 9986          });
 9987      }
 9988  #endif // __ZOOMIT_POWERTOYS__
 9989  
 9990  
 9991  #ifndef _WIN64
 9992  
 9993      if(!g_StartedByPowerToys)
 9994      {
 9995          // Launch 64-bit version if necessary
 9996          SetAutostartFilePath();
 9997          if( RunningOnWin64()) {
 9998  
 9999              // Record where we are if we're the 32-bit version
10000              return Run64bitVersion();
10001          }
10002      }
10003  #endif
10004  
10005      // Single instance per desktop
10006  
10007      if( !CreateEvent( NULL, FALSE, FALSE, _T("Local\\ZoomitActive"))) {
10008  
10009          CreateEvent( NULL, FALSE, FALSE, _T("ZoomitActive"));
10010      }
10011      if( GetLastError() == ERROR_ALREADY_EXISTS ) {
10012          if (g_StartedByPowerToys)
10013          {
10014              MessageBox(NULL, L"We've detected another instance of ZoomIt is already running.\nCan't start a new ZoomIt instance from PowerToys.",
10015              APPNAME, MB_ICONERROR | MB_SETFOREGROUND);
10016              return 1;
10017          }
10018  
10019          // Tell the other instance to show the options dialog
10020          g_hWndMain = FindWindow( L"ZoomitClass", NULL );
10021          if( g_hWndMain != NULL ) {
10022  
10023              PostMessage( g_hWndMain, WM_COMMAND, IDC_OPTIONS, 0 );
10024              int count = 0;
10025              while( count++ < 5 ) {
10026  
10027                  HWND local_hWndOptions = FindWindow( NULL, L"ZoomIt - Sysinternals: www.sysinternals.com" );
10028                  if( local_hWndOptions ) {
10029  
10030                      SetForegroundWindow( local_hWndOptions );
10031                      SetWindowPos( local_hWndOptions, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_SHOWWINDOW );
10032                      break;
10033                  }
10034                  Sleep( 100 );
10035              }
10036          }
10037          return 0;
10038      }
10039  
10040      g_OsVersion = GetVersion() & 0xFFFF;
10041  
10042      // load accelerators
10043      hAccel = LoadAccelerators( hInstance, TEXT("ACCELERATORS"));
10044  
10045      if (FAILED(CoInitialize(0)))
10046      {
10047          return 0;
10048      }
10049  
10050      pEnableThemeDialogTexture = (type_pEnableThemeDialogTexture) GetProcAddress( GetModuleHandle( L"uxtheme.dll" ),
10051                      "EnableThemeDialogTexture" );
10052  
10053      // Initialize dark mode support early, before any windows are created
10054      // This is required for popup menus to use dark mode
10055      InitializeDarkMode();
10056  
10057      pMonitorFromPoint = (type_MonitorFromPoint) GetProcAddress( LoadLibrarySafe( L"User32.dll", DLL_LOAD_LOCATION_SYSTEM),
10058                      "MonitorFromPoint" );
10059      pGetMonitorInfo = (type_pGetMonitorInfo) GetProcAddress( LoadLibrarySafe( L"User32.dll", DLL_LOAD_LOCATION_SYSTEM),
10060                      "GetMonitorInfoA" );
10061      pSHAutoComplete = (type_pSHAutoComplete) GetProcAddress( LoadLibrarySafe(L"Shlwapi.dll", DLL_LOAD_LOCATION_SYSTEM),
10062                      "SHAutoComplete" );
10063      pSetLayeredWindowAttributes = (type_pSetLayeredWindowAttributes) GetProcAddress( LoadLibrarySafe(L"user32.dll", DLL_LOAD_LOCATION_SYSTEM),
10064                      "SetLayeredWindowAttributes" );
10065      pMagSetWindowSource = (type_pMagSetWindowSource) GetProcAddress( LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM),
10066                      "MagSetWindowSource" );
10067      pGetPointerType = (type_pGetPointerType)GetProcAddress(LoadLibrarySafe(L"user32.dll", DLL_LOAD_LOCATION_SYSTEM),
10068                      "GetPointerType" );
10069      pGetPointerPenInfo = (type_pGetPointerPenInfo)GetProcAddress(LoadLibrarySafe(L"user32.dll", DLL_LOAD_LOCATION_SYSTEM),
10070                      "GetPointerPenInfo" );
10071      pMagInitialize = (type_pMagInitialize)GetProcAddress(LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM),
10072                      "MagInitialize");
10073      pMagSetWindowTransform = (type_pMagSetWindowTransform) GetProcAddress( LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM),
10074                      "MagSetWindowTransform" );
10075      pMagSetFullscreenTransform = (type_pMagSetFullscreenTransform)GetProcAddress(LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM),
10076                      "MagSetFullscreenTransform");
10077      pMagSetFullscreenUseBitmapSmoothing = (type_MagSetFullscreenUseBitmapSmoothing)GetProcAddress(LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM),
10078                      "MagSetFullscreenUseBitmapSmoothing");
10079      pMagSetLensUseBitmapSmoothing = (type_pMagSetLensUseBitmapSmoothing)GetProcAddress(LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM),
10080                      "MagSetLensUseBitmapSmoothing");
10081      pMagSetInputTransform = (type_pMagSetInputTransform)GetProcAddress(LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM),
10082                      "MagSetInputTransform");
10083      pMagShowSystemCursor = (type_pMagShowSystemCursor)GetProcAddress(LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM),
10084                      "MagShowSystemCursor");
10085      pMagSetWindowFilterList = (type_pMagSetWindowFilterList)GetProcAddress( LoadLibrarySafe( L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM ),
10086                      "MagSetWindowFilterList" );
10087      pSHQueryUserNotificationState = (type_pSHQueryUserNotificationState) GetProcAddress( LoadLibrarySafe(L"shell32.dll", DLL_LOAD_LOCATION_SYSTEM),
10088                      "SHQueryUserNotificationState" );
10089      pDwmIsCompositionEnabled = (type_pDwmIsCompositionEnabled) GetProcAddress( LoadLibrarySafe(L"dwmapi.dll", DLL_LOAD_LOCATION_SYSTEM),
10090                      "DwmIsCompositionEnabled" );
10091      pSetProcessDPIAware = (type_pSetProcessDPIAware) GetProcAddress( LoadLibrarySafe(L"User32.dll", DLL_LOAD_LOCATION_SYSTEM),
10092                      "SetProcessDPIAware");
10093      pSystemParametersInfoForDpi = (type_pSystemParametersInfoForDpi)GetProcAddress(LoadLibrarySafe(L"User32.dll", DLL_LOAD_LOCATION_SYSTEM),
10094                      "SystemParametersInfoForDpi");
10095      pGetDpiForWindow = (type_pGetDpiForWindow)GetProcAddress(LoadLibrarySafe(L"User32.dll", DLL_LOAD_LOCATION_SYSTEM),
10096                      "GetDpiForWindow" );
10097      pCreateDirect3D11DeviceFromDXGIDevice = (type_pCreateDirect3D11DeviceFromDXGIDevice) GetProcAddress( LoadLibrarySafe(L"d3d11.dll", DLL_LOAD_LOCATION_SYSTEM),
10098                      "CreateDirect3D11DeviceFromDXGIDevice" );
10099      pCreateDirect3D11SurfaceFromDXGISurface = (type_pCreateDirect3D11SurfaceFromDXGISurface) GetProcAddress( LoadLibrarySafe(L"d3d11.dll", DLL_LOAD_LOCATION_SYSTEM),
10100                      "CreateDirect3D11SurfaceFromDXGISurface" );
10101      pD3D11CreateDevice = (type_pD3D11CreateDevice) GetProcAddress( LoadLibrarySafe(L"d3d11.dll", DLL_LOAD_LOCATION_SYSTEM),
10102                      "D3D11CreateDevice" );
10103  
10104      // Windows Server 2022 (and including Windows 11) introduced a bug where the cursor disappears
10105      // in live zoom. Use the full-screen magnifier as a workaround on those versions only. It is
10106      // currently impractical as a replacement; it requires calling MagSetInputTransform for all
10107      // input to be transformed. Else, some hit-testing is misdirected. MagSetInputTransform
10108      // fails without token UI access, which is impractical; it requires copying the executable
10109      // under either %ProgramFiles% or %SystemRoot%, which requires elevation.
10110      //
10111      // TODO: Update the Windows 11 21H2 revision check when the final number is known. Also add a
10112      //       check for the Windows Server 2022 revision if that bug (https://task.ms/38611091) is
10113      //       fixed.
10114      DWORD windowsRevision, windowsBuild = GetWindowsBuild( &windowsRevision );
10115      if( ( windowsBuild == BUILD_WINDOWS_SERVER_2022 ) ||
10116          ( ( windowsBuild == BUILD_WINDOWS_11_21H2 ) && ( windowsRevision < 829 ) ) ) {
10117  
10118          if( pMagSetFullscreenTransform && pMagSetInputTransform )
10119              g_fullScreenWorkaround = TRUE;
10120      }
10121  
10122  #if 1
10123      // Calling this causes Windows to mess with our query of monitor height and width
10124      if( pSetProcessDPIAware ) {
10125  
10126          pSetProcessDPIAware();
10127      }
10128  #endif
10129      /* Perform initializations that apply to a specific instance */
10130      g_hWndMain = InitInstance(hInstance, nCmdShow);
10131      if (!g_hWndMain )
10132          return FALSE;
10133  
10134  #ifdef __ZOOMIT_POWERTOYS__
10135      HANDLE m_reload_settings_event_handle = NULL;
10136      HANDLE m_exit_event_handle = NULL;
10137      HANDLE m_zoom_event_handle = NULL;
10138      HANDLE m_draw_event_handle = NULL;
10139      HANDLE m_break_event_handle = NULL;
10140      HANDLE m_live_zoom_event_handle = NULL;
10141      HANDLE m_snip_event_handle = NULL;
10142      HANDLE m_record_event_handle = NULL;
10143      std::thread m_event_triggers_thread;
10144  
10145      if( g_StartedByPowerToys ) {
10146          // Start a thread to listen to PowerToys Events.
10147          m_reload_settings_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_REFRESH_SETTINGS_EVENT);
10148          m_exit_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_EXIT_EVENT);
10149          m_zoom_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_ZOOM_EVENT);
10150          m_draw_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_DRAW_EVENT);
10151          m_break_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_BREAK_EVENT);
10152          m_live_zoom_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_LIVEZOOM_EVENT);
10153          m_snip_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_SNIP_EVENT);
10154          m_record_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_RECORD_EVENT);
10155          if (!m_reload_settings_event_handle || !m_exit_event_handle || !m_zoom_event_handle || !m_draw_event_handle || !m_break_event_handle || !m_live_zoom_event_handle || !m_snip_event_handle || !m_record_event_handle)
10156          {
10157              Logger::warn(L"Failed to create events. {}", get_last_error_or_default(GetLastError()));
10158              return 1;
10159          }
10160          const std::array<HANDLE, 8> event_handles{
10161              m_reload_settings_event_handle,
10162              m_exit_event_handle,
10163              m_zoom_event_handle,
10164              m_draw_event_handle,
10165              m_break_event_handle,
10166              m_live_zoom_event_handle,
10167              m_snip_event_handle,
10168              m_record_event_handle,
10169          };
10170          const DWORD handle_count = static_cast<DWORD>(event_handles.size());
10171          m_event_triggers_thread = std::thread([event_handles, handle_count]() {
10172              MSG msg;
10173              while (g_running)
10174              {
10175                  DWORD dwEvt = MsgWaitForMultipleObjects(handle_count, event_handles.data(), false, INFINITE, QS_ALLINPUT);
10176                  if (dwEvt == WAIT_FAILED)
10177                  {
10178                      Logger::error(L"ZoomIt event wait failed. {}", get_last_error_or_default(GetLastError()));
10179                      break;
10180                  }
10181                  if (!g_running)
10182                  {
10183                      break;
10184                  }
10185                  if (dwEvt == WAIT_OBJECT_0 + handle_count)
10186                  {
10187                      if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
10188                      {
10189                          TranslateMessage(&msg);
10190                          DispatchMessageW(&msg);
10191                      }
10192                      continue;
10193                  }
10194                  switch (dwEvt)
10195                  {
10196                  case WAIT_OBJECT_0:
10197                  {
10198                      // Reload Settings Event
10199                      Logger::trace(L"Received a reload settings event.");
10200                      PostMessage(g_hWndMain, WM_USER_RELOAD_SETTINGS, 0, 0);
10201                      break;
10202                  }
10203                  case WAIT_OBJECT_0 + 1:
10204                  {
10205                      // Exit Event
10206                      PostMessage(g_hWndMain, WM_QUIT, 0, 0);
10207                      break;
10208                  }
10209                  case WAIT_OBJECT_0 + 2:
10210                      ZoomIt_DispatchCommand(ZoomItCommand::Zoom);
10211                      break;
10212                  case WAIT_OBJECT_0 + 3:
10213                      ZoomIt_DispatchCommand(ZoomItCommand::Draw);
10214                      break;
10215                  case WAIT_OBJECT_0 + 4:
10216                      ZoomIt_DispatchCommand(ZoomItCommand::Break);
10217                      break;
10218                  case WAIT_OBJECT_0 + 5:
10219                      ZoomIt_DispatchCommand(ZoomItCommand::LiveZoom);
10220                      break;
10221                  case WAIT_OBJECT_0 + 6:
10222                      ZoomIt_DispatchCommand(ZoomItCommand::Snip);
10223                      break;
10224                  case WAIT_OBJECT_0 + 7:
10225                      ZoomIt_DispatchCommand(ZoomItCommand::Record);
10226                      break;
10227                  default: break;
10228                  }
10229              }
10230          });
10231      }
10232  #endif // __ZOOMIT_POWERTOYS__
10233  
10234      /* Acquire and dispatch messages until a WM_QUIT message is received. */
10235      while (GetMessage(&msg,	NULL, 0, 0 ))  {
10236          if( !TranslateAccelerator( g_hWndMain, hAccel, &msg )) {
10237              TranslateMessage(&msg);
10238              DispatchMessage(&msg);
10239          }
10240      }
10241      int retCode = (int) msg.wParam;
10242  
10243      g_running = FALSE;
10244  
10245  #ifdef __ZOOMIT_POWERTOYS__
10246      if(g_StartedByPowerToys)
10247      {
10248          if (trace!=nullptr) {
10249              trace->Flush();
10250              delete trace;
10251          }
10252          Trace::UnregisterProvider();
10253          // Needed to unblock MsgWaitForMultipleObjects one last time
10254          SetEvent(m_reload_settings_event_handle);
10255          CloseHandle(m_reload_settings_event_handle);
10256          CloseHandle(m_exit_event_handle);
10257          CloseHandle(m_zoom_event_handle);
10258          CloseHandle(m_draw_event_handle);
10259          CloseHandle(m_break_event_handle);
10260          CloseHandle(m_live_zoom_event_handle);
10261          CloseHandle(m_snip_event_handle);
10262          CloseHandle(m_record_event_handle);
10263          m_event_triggers_thread.join();
10264      }
10265  #endif // __ZOOMIT_POWERTOYS__
10266  
10267      return retCode;
10268  }