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 ), ¤tLf ) ) 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 }