DemoType.cpp
1 //============================================================================ 2 // 3 // Zoomit 4 // Copyright (C) Mark Russinovich 5 // Sysinternals - www.sysinternals.com 6 // 7 // DemoType allows the presenter to synthesize keystrokes from a script 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 13 #include "pch.h" 14 #include "DemoType.h" 15 16 #define MAX_INDENT_DEPTH 100 17 18 #define INDENT_SEEK_FLAG L"x" 19 20 #define END_CONTROL_LEN 5 21 // Longest accepted control: [pause:000] 22 #define MAX_CONTROL_LEN 11 23 24 #define THIRD_TYPING_SPEED static_cast<int>((MIN_TYPING_SPEED - MAX_TYPING_SPEED) / 3) 25 #define TYPING_VARIANCE ((float) 1.0) 26 27 #define NOTEPAD_REFRESH 1 // ms 28 #define DEMOTYPE_REFRESH 50 // ms 29 #define CLIPBOARD_REFRESH 100 // ms 30 #define DEMOTYPE_TIMEOUT 1000 // ms 31 32 #define INACTIVE_STATE 0 33 #define START_STATE 1 34 #define INIT_STATE 2 35 #define ACTIVE_STATE 3 36 #define BLOCK_STATE 4 37 #define KILL_STATE 5 38 39 // Each injection is tracked so that the hook 40 // procedure can identify injections and allow them 41 // to pass through while blocking accidental keystrokes. 42 // 43 // Each injection is identified by either a virtual 44 // key code or a unicode character passed as a scan code 45 // which is wrapped into a VK_PACKET by SendInput when 46 // the KEYEVENTF_UNICODE flag is specified. 47 // 48 // VK_PACKET allows us to synthesize keystrokes which are 49 // not mapped to virtual-key codes (e.g. foreign characters). 50 struct Injection 51 { 52 DWORD vkCode; 53 DWORD scanCode; 54 55 Injection( DWORD vkCode, DWORD scanCode ) 56 : vkCode(vkCode), scanCode(scanCode) {} 57 }; 58 59 bool g_UserDriven = false; 60 bool g_Notepad = false; 61 bool g_Clipboard = false; 62 TCHAR g_LastFilePath[MAX_PATH] = {0}; 63 HHOOK g_hHook = nullptr; 64 size_t g_TextLen = 0; 65 wchar_t* g_ClipboardCache = nullptr; 66 std::wstring g_Text = L""; 67 std::vector<size_t> g_TextSegments; 68 std::wstring g_BaselineIndentation = L""; 69 std::atomic<size_t> g_Index = 0; 70 std::condition_variable g_EpochReady; 71 std::mutex g_EpochMutex; 72 std::deque<Injection> g_Injections; 73 std::mutex g_InjectionsMutex; 74 std::atomic<bool> g_Active = false; 75 std::atomic<bool> g_End = false; 76 std::atomic<bool> g_Kill = false; 77 std::atomic<int> g_HookState = INACTIVE_STATE; 78 std::atomic<int> g_EmitterState = INACTIVE_STATE; 79 DWORD g_LastClipboardSeq = 0; 80 DWORD g_SpeedSlider = static_cast<int>(( 81 (MIN_TYPING_SPEED - MAX_TYPING_SPEED) / 2) + MAX_TYPING_SPEED); 82 83 //---------------------------------------------------------------------------- 84 // 85 // IsWindowNotepad 86 // 87 //---------------------------------------------------------------------------- 88 bool IsWindowNotepad( const HWND hwnd ) 89 { 90 const int CLASS_NAME_LEN = 256; 91 WCHAR className[CLASS_NAME_LEN]; 92 if( GetClassName( hwnd, className, CLASS_NAME_LEN ) > 0 ) 93 { 94 if( wcscmp( className, L"Notepad" ) == 0 ) 95 { 96 return true; 97 } 98 } 99 return false; 100 } 101 102 //---------------------------------------------------------------------------- 103 // 104 // IsInjected 105 // 106 //---------------------------------------------------------------------------- 107 bool IsInjected( const DWORD vkCode, const DWORD scanCode ) 108 { 109 bool injected = false; 110 bool locked = false; 111 if( g_EmitterState == ACTIVE_STATE ) 112 { 113 g_InjectionsMutex.lock(); 114 locked = true; 115 } 116 117 if( !g_Injections.empty() ) 118 { 119 if( (g_Injections.front().vkCode != NULL && g_Injections.front().vkCode == vkCode) 120 || (g_Injections.front().vkCode == NULL && g_Injections.front().scanCode == scanCode) ) 121 { 122 injected = true; 123 } 124 } 125 126 if( locked ) 127 { 128 g_InjectionsMutex.unlock(); 129 } 130 return injected; 131 } 132 133 //---------------------------------------------------------------------------- 134 // 135 // IsAutoFormatTrigger 136 // 137 //---------------------------------------------------------------------------- 138 bool IsAutoFormatTrigger( wchar_t lastCh, wchar_t ch ) 139 { 140 // Will trigger auto-indentation in smart editors 141 // '\t' check also handles possible auto-completion 142 if( ch == L'\n' || ch == L'\t' || (ch == L' ' && lastCh == L'\n') ) 143 { 144 return true; 145 } 146 147 // Will trigger auto-close character(s) in smart editors 148 if( ch == L'{' || ch == L'[' || ch == L'(' || (ch == L'*' && lastCh == L'/') ) 149 { 150 return true; 151 } 152 153 return false; 154 } 155 156 //---------------------------------------------------------------------------- 157 // 158 // PopInjection 159 // 160 // See comments above `Injection` struct definition 161 // 162 //---------------------------------------------------------------------------- 163 void PopInjection() 164 { 165 bool locked = false; 166 if( g_EmitterState == ACTIVE_STATE ) 167 { 168 g_InjectionsMutex.lock(); 169 locked = true; 170 } 171 172 g_Injections.pop_front(); 173 174 if( locked ) 175 { 176 g_InjectionsMutex.unlock(); 177 } 178 } 179 180 //---------------------------------------------------------------------------- 181 // 182 // PushInjection 183 // 184 // See comments above `Injection` struct definition 185 // 186 //---------------------------------------------------------------------------- 187 void PushInjection( const WORD vK, const wchar_t ch ) 188 { 189 bool locked = false; 190 if( g_EmitterState == ACTIVE_STATE ) 191 { 192 g_InjectionsMutex.lock(); 193 locked = true; 194 } 195 196 g_Injections.push_back( Injection( static_cast<DWORD>(vK), static_cast<DWORD>(ch) ) ); 197 198 if( locked ) 199 { 200 g_InjectionsMutex.unlock(); 201 } 202 } 203 204 //---------------------------------------------------------------------------- 205 // 206 // IsNotPrintable 207 // 208 //---------------------------------------------------------------------------- 209 bool IsNotPrintable( wchar_t ch ) 210 { 211 return ch != L'\n' && ch != L'\t' && !iswprint( ch ); 212 } 213 214 //---------------------------------------------------------------------------- 215 // 216 // SendKeyInput 217 // 218 //---------------------------------------------------------------------------- 219 void SendKeyInput( const WORD vK, const wchar_t ch, const bool keyup = false ) 220 { 221 INPUT input = {0}; 222 input.type = INPUT_KEYBOARD; 223 224 // Send unicode character via VK_PACKET 225 if( vK == NULL ) 226 { 227 input.ki.wScan = ch; 228 input.ki.dwFlags = KEYEVENTF_UNICODE; 229 } 230 // Send virtual-key code 231 else 232 { 233 input.ki.wVk = vK; 234 235 if( vK == VK_RCONTROL || vK == VK_RMENU || vK == VK_LEFT 236 || vK == VK_RIGHT || vK == VK_UP || vK == VK_DOWN ) 237 { 238 input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; 239 } 240 } 241 242 if( keyup ) 243 { 244 input.ki.dwFlags |= KEYEVENTF_KEYUP; 245 } 246 247 SendInput( 1, &input, sizeof( INPUT ) ); 248 249 // Add latency between keydown/up to accomodate notepad input handling 250 if( !keyup && g_Notepad ) 251 { 252 std::this_thread::sleep_for( std::chrono::milliseconds( NOTEPAD_REFRESH ) ); 253 } 254 } 255 256 //---------------------------------------------------------------------------- 257 // 258 // SendUnicodeKeyDown 259 // 260 //---------------------------------------------------------------------------- 261 void SendUnicodeKeyDown( const wchar_t ch ) 262 { 263 PushInjection( NULL, ch ); 264 SendKeyInput ( NULL, ch ); 265 } 266 267 //---------------------------------------------------------------------------- 268 // 269 // SendUnicodeKeyUp 270 // 271 //---------------------------------------------------------------------------- 272 void SendUnicodeKeyUp( const wchar_t ch ) 273 { 274 PushInjection( NULL, ch ); 275 SendKeyInput ( NULL, ch, true ); 276 } 277 278 //---------------------------------------------------------------------------- 279 // 280 // SendVirtualKeyDown 281 // 282 //---------------------------------------------------------------------------- 283 void SendVirtualKeyDown( const WORD vK ) 284 { 285 PushInjection( vK, NULL ); 286 SendKeyInput ( vK, NULL ); 287 } 288 289 //---------------------------------------------------------------------------- 290 // 291 // SendVirtualKeyUp 292 // 293 //---------------------------------------------------------------------------- 294 void SendVirtualKeyUp( const WORD vK ) 295 { 296 PushInjection( vK, NULL ); 297 SendKeyInput ( vK, NULL, true ); 298 } 299 300 //---------------------------------------------------------------------------- 301 // 302 // GetRandomNumber 303 // 304 //---------------------------------------------------------------------------- 305 unsigned int GetRandomNumber( unsigned int lower, unsigned int upper ) 306 { 307 return lower + std::rand() % (upper - lower + 1); 308 } 309 310 //---------------------------------------------------------------------------- 311 // 312 // BlockModifierKeys 313 // 314 //---------------------------------------------------------------------------- 315 int BlockModifierKeys() 316 { 317 int blockDepth = 0; 318 const std::vector<WORD> MODIFIERS = { VK_LSHIFT, VK_RSHIFT, 319 VK_LCONTROL, VK_RCONTROL, VK_LMENU, VK_RMENU }; 320 321 if( (GetKeyState( VK_CAPITAL ) & 0x0001) != 0 ) 322 { 323 SendVirtualKeyDown( VK_CAPITAL ); 324 SendVirtualKeyUp ( VK_CAPITAL ); 325 } 326 for( auto modifier : MODIFIERS ) 327 { 328 if( (GetKeyState( modifier ) & 0x8000) != 0 ) 329 { 330 blockDepth++; 331 SendVirtualKeyUp( modifier ); 332 } 333 } 334 335 return blockDepth; 336 } 337 338 //---------------------------------------------------------------------------- 339 // 340 // GetClipboardSequence 341 // 342 //---------------------------------------------------------------------------- 343 DWORD GetClipboardSequence() 344 { 345 DWORD sequence; 346 if( !OpenClipboard( nullptr ) ) 347 { 348 CloseClipboard(); 349 return 0; 350 } 351 sequence = GetClipboardSequenceNumber(); 352 CloseClipboard(); 353 return sequence; 354 } 355 356 //---------------------------------------------------------------------------- 357 // 358 // GetClipboard 359 // 360 //---------------------------------------------------------------------------- 361 wchar_t* GetClipboard() 362 { 363 // Confirm clipboard accessibility and data format 364 if( !OpenClipboard( nullptr ) && !IsClipboardFormatAvailable( CF_UNICODETEXT ) ) 365 { 366 CloseClipboard(); 367 return nullptr; 368 } 369 HANDLE hData = GetClipboardData( CF_UNICODETEXT ); 370 if( hData == nullptr ) 371 { 372 CloseClipboard(); 373 return nullptr; 374 } 375 376 // Confirm clipboard size doesn't exceed MAX_INPUT_SIZE 377 size_t size = GlobalSize( hData ); 378 if( size <= 0 || size > MAX_INPUT_SIZE ) 379 { 380 GlobalUnlock( hData ); 381 CloseClipboard(); 382 return nullptr; 383 } 384 385 const wchar_t* pData = static_cast<wchar_t*>(GlobalLock( hData )); 386 if( pData == nullptr ) 387 { 388 GlobalUnlock( hData ); 389 CloseClipboard(); 390 return nullptr; 391 } 392 393 wchar_t* data = new wchar_t[size / sizeof(wchar_t)]; 394 wcscpy( data, pData ); 395 GlobalUnlock( hData ); 396 CloseClipboard(); 397 return data; 398 } 399 400 //---------------------------------------------------------------------------- 401 // 402 // SetClipboard 403 // 404 //---------------------------------------------------------------------------- 405 bool SetClipboard( const wchar_t* data ) 406 { 407 if( data == nullptr ) 408 { 409 return false; 410 } 411 if( !OpenClipboard( nullptr ) ) 412 { 413 CloseClipboard(); 414 return false; 415 } 416 EmptyClipboard(); 417 418 size_t size = (wcslen( data ) + 1) * sizeof( wchar_t ); 419 HGLOBAL hData = GlobalAlloc( GMEM_MOVEABLE, size ); 420 if( hData == nullptr ) 421 { 422 CloseClipboard(); 423 return false; 424 } 425 426 wchar_t* pData = static_cast<wchar_t*>(GlobalLock( hData )); 427 if( pData == nullptr ) 428 { 429 GlobalUnlock( hData ); 430 GlobalFree( hData ); 431 CloseClipboard(); 432 return false; 433 } 434 435 wcscpy( pData, data ); 436 GlobalUnlock( hData ); 437 SetClipboardData( CF_UNICODETEXT, hData ); 438 CloseClipboard(); 439 return true; 440 } 441 442 //---------------------------------------------------------------------------- 443 // 444 // GetBaselineIndentation 445 // 446 //---------------------------------------------------------------------------- 447 void GetBaselineIndentation() 448 { 449 size_t len = 0; 450 size_t lastLen = 0; 451 bool resetCursor = true; 452 wchar_t* seekBuffer = nullptr; 453 static const WORD VK_C = static_cast<WORD>(LOBYTE( VkKeyScan( L'c' ) )); 454 455 // VS fakes newline indentation until the user adds input 456 SendVirtualKeyDown( VK_SPACE ); 457 SendVirtualKeyUp ( VK_SPACE ); 458 SendVirtualKeyDown( VK_BACK ); 459 SendVirtualKeyUp ( VK_BACK ); 460 461 for( int i = 0; i < MAX_INDENT_DEPTH; i++ ) 462 { 463 SendVirtualKeyDown( VK_LSHIFT ); 464 SendVirtualKeyDown( VK_LEFT ); 465 SendVirtualKeyUp ( VK_LEFT ); 466 SendVirtualKeyUp ( VK_LSHIFT ); 467 468 SendVirtualKeyDown( VK_LCONTROL ); 469 SendVirtualKeyDown( VK_C ); 470 SendVirtualKeyUp ( VK_C ); 471 SendVirtualKeyUp ( VK_LCONTROL ); 472 473 std::this_thread::sleep_for( std::chrono::milliseconds( CLIPBOARD_REFRESH ) ); 474 475 len = 0; 476 delete[] seekBuffer; 477 seekBuffer = GetClipboard(); 478 if( seekBuffer == nullptr ) 479 { 480 resetCursor = false; 481 break; 482 } 483 len = wcslen( seekBuffer ); 484 485 if( g_LastClipboardSeq == GetClipboardSequence() ) 486 { 487 resetCursor = false; 488 break; 489 } 490 else if( seekBuffer[0] == L'\n' || seekBuffer[0] == L'\r' ) 491 { 492 break; 493 } 494 else if( len == lastLen ) 495 { 496 if( len == 0 ) 497 { 498 resetCursor = false; 499 } 500 break; 501 } 502 lastLen = len; 503 } 504 505 if( resetCursor ) 506 { 507 SendVirtualKeyDown( VK_RIGHT ); 508 SendVirtualKeyUp ( VK_RIGHT ); 509 } 510 511 // Extract line indentation 512 g_BaselineIndentation.clear(); 513 for( size_t i = 0; i < len; i++ ) 514 { 515 if( iswprint( seekBuffer[i] ) && seekBuffer[i] != L' ' ) 516 { 517 break; 518 } 519 else if( seekBuffer[i] == L'\t' || seekBuffer[i] == L' ' ) 520 { 521 g_BaselineIndentation.push_back( seekBuffer[i] ); 522 } 523 } 524 525 delete[] seekBuffer; 526 } 527 528 //---------------------------------------------------------------------------- 529 // 530 // InjectByClipboard 531 // 532 // Editors handle paste operations slowly so we use this method sparingly 533 // 534 //---------------------------------------------------------------------------- 535 wchar_t InjectByClipboard( wchar_t lastCh, wchar_t ch, const std::wstring& override = L"" ) 536 { 537 int i = 0; 538 bool trim = false; 539 bool chunk = false; 540 std::wstring injection(1, ch); 541 static const WORD VK_V = static_cast<WORD>(LOBYTE( VkKeyScan( L'v' ) )); 542 543 if( override == L"" ) 544 { 545 if( ch == L'\n' && g_BaselineIndentation != L"" && g_BaselineIndentation != L"x" ) 546 { 547 injection.append( g_BaselineIndentation ); 548 } 549 550 // VS absorbs pasted line indentation so we inject it as a chunk of indents and the first printable ch 551 if( lastCh == L'\n' && (ch == L'\t' || ch == L' ') ) 552 { 553 chunk = true; 554 for( i = 1; g_Index + i < g_TextLen; i++ ) 555 { 556 injection.push_back( g_Text[g_Index + i] ); 557 if( g_Text[g_Index + i] != L' ' && iswprint( g_Text[g_Index + i] ) ) 558 { 559 if( g_Text[g_Index + i] == L'[' ) 560 { 561 trim = true; 562 } 563 break; 564 } 565 } 566 } 567 } 568 569 std::this_thread::sleep_for( std::chrono::milliseconds( CLIPBOARD_REFRESH ) ); 570 if( !SetClipboard( override == L"" ? injection.c_str() : override.c_str() ) ) 571 { 572 std::this_thread::sleep_for( std::chrono::milliseconds( CLIPBOARD_REFRESH ) ); 573 SetClipboard( override == L"" ? injection.c_str() : override.c_str() ); 574 } 575 std::this_thread::sleep_for( std::chrono::milliseconds( CLIPBOARD_REFRESH ) ); 576 577 SendVirtualKeyDown( VK_LCONTROL ); 578 SendVirtualKeyDown( VK_V ); 579 SendVirtualKeyUp ( VK_V ); 580 SendVirtualKeyUp ( VK_LCONTROL ); 581 582 std::this_thread::sleep_for( std::chrono::milliseconds( CLIPBOARD_REFRESH ) ); 583 584 // Trim the last character from our chunk if it was [ 585 // Because it might be the start of a control keyword 586 if( trim ) 587 { 588 SendVirtualKeyDown( VK_BACK ); 589 SendVirtualKeyUp ( VK_BACK ); 590 591 g_Index += static_cast<unsigned long long>(i) - 1; 592 return g_Text[g_Index]; 593 } 594 else if( chunk ) 595 { 596 g_Index += i; 597 return g_Text[g_Index]; 598 } 599 return NULL; 600 } 601 602 //---------------------------------------------------------------------------- 603 // 604 // HandleControlKeyword 605 // 606 //---------------------------------------------------------------------------- 607 bool HandleControlKeyword() 608 { 609 size_t controlClose = g_Text.find( L']', g_Index ); 610 size_t controlLen = controlClose - g_Index + 1; 611 612 if( controlLen <= MAX_CONTROL_LEN ) 613 { 614 std::wstring control = g_Text.substr( g_Index, controlLen ); 615 616 if( control == L"[end]" ) 617 { 618 g_End = true; 619 g_Index += controlLen; 620 g_TextSegments.push_back( g_Index ); 621 622 // In standard mode, [end] is interpreted as an immediate kill signal 623 if( !g_UserDriven ) 624 { 625 g_EmitterState = KILL_STATE; 626 } 627 628 return true; 629 } 630 else if( control.substr( 0, 7 ) == L"[pause:" ) 631 { 632 g_Index += controlLen; 633 634 if( g_UserDriven ) 635 { 636 return true; 637 } 638 639 std::wistringstream iss(control.substr( 7, control.length() - 2 )); 640 unsigned int time; 641 642 if( iss >> time ) 643 { 644 if( time > 0 ) 645 { 646 // Pause but poll for termination 647 for( int i = 0; i < static_cast<int>(1000 / DEMOTYPE_REFRESH * time); i++ ) 648 { 649 if( g_EmitterState == KILL_STATE ) 650 { 651 break; 652 } 653 std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) ); 654 } 655 return true; 656 } 657 } 658 } 659 else if( control == L"[paste]" ) 660 { 661 size_t endControlOpen = g_Text.find( L"[/paste]", controlClose ); 662 if( endControlOpen != std::wstring::npos ) 663 { 664 size_t endControlClose = g_Text.find( L']', endControlOpen ); 665 size_t endControlLen = endControlClose - g_Index + 1; 666 667 std::wstring pasteData = g_Text.substr( controlClose + 1, endControlOpen - controlClose - 1 ); 668 InjectByClipboard( NULL, NULL, pasteData ); 669 670 g_Index += endControlLen; 671 return true; 672 } 673 } 674 else 675 { 676 if( control == L"[enter]" ) 677 { 678 SendVirtualKeyDown( VK_RETURN ); 679 SendVirtualKeyUp ( VK_RETURN ); 680 } 681 else if( control == L"[up]" ) 682 { 683 SendVirtualKeyDown( VK_UP ); 684 SendVirtualKeyUp ( VK_UP ); 685 } 686 else if( control == L"[down]" ) 687 { 688 SendVirtualKeyDown( VK_DOWN ); 689 SendVirtualKeyUp ( VK_DOWN ); 690 } 691 else if( control == L"[left]" ) 692 { 693 SendVirtualKeyDown( VK_LEFT ); 694 SendVirtualKeyUp ( VK_LEFT ); 695 } 696 else if( control == L"[right]" ) 697 { 698 SendVirtualKeyDown( VK_RIGHT ); 699 SendVirtualKeyUp ( VK_RIGHT ); 700 } 701 else 702 { 703 return false; 704 } 705 g_Index += controlLen; 706 return true; 707 } 708 } 709 return false; 710 } 711 712 //---------------------------------------------------------------------------- 713 // 714 // HandleInjection 715 // 716 //---------------------------------------------------------------------------- 717 void HandleInjection( bool init = false ) 718 { 719 static wchar_t lastCh = NULL; 720 721 if( init ) 722 { 723 if( g_Index == 0 ) 724 { 725 g_TextSegments.clear(); 726 } 727 728 lastCh = NULL; 729 GetBaselineIndentation(); 730 return; 731 } 732 733 wchar_t ch = g_Text[g_Index]; 734 735 if( ch == L'[' ) 736 { 737 if( HandleControlKeyword() ) 738 { 739 return; 740 } 741 } 742 743 if( IsAutoFormatTrigger( lastCh, ch ) ) 744 { 745 wchar_t newCh = InjectByClipboard( lastCh, ch ); 746 if( newCh != NULL ) 747 { 748 ch = newCh; 749 } 750 } 751 else 752 { 753 SendUnicodeKeyDown( ch ); 754 SendUnicodeKeyUp ( ch ); 755 } 756 lastCh = ch; 757 g_Index++; 758 } 759 760 //---------------------------------------------------------------------------- 761 // 762 // DemoTypeEmitter 763 // 764 //---------------------------------------------------------------------------- 765 void DemoTypeEmitter() 766 { 767 const unsigned int speed = static_cast<unsigned int>((MIN_TYPING_SPEED + MAX_TYPING_SPEED) - g_SpeedSlider); 768 const unsigned int variance = static_cast<unsigned int>(speed * TYPING_VARIANCE); 769 770 // Initialize the injection handler 771 HandleInjection( true ); 772 773 while( g_EmitterState == ACTIVE_STATE && g_Index < g_TextLen ) 774 { 775 HandleInjection(); 776 777 std::this_thread::sleep_for( std::chrono::milliseconds( 778 GetRandomNumber( max( speed - variance, 1 ), max( speed + variance, 1 ) ) ) ); 779 } 780 if( g_Index >= g_TextLen ) 781 { 782 g_Index = 0; 783 784 // Synthesize [end] at end of script if no [end] is present 785 if( !g_End ) 786 { 787 g_End = true; 788 g_TextSegments.push_back( g_Index ); 789 } 790 } 791 792 g_EmitterState = INACTIVE_STATE; 793 g_Kill = true; 794 { 795 std::lock_guard<std::mutex> epochLock(g_EpochMutex); 796 } 797 g_EpochReady.notify_one(); 798 } 799 800 //---------------------------------------------------------------------------- 801 // 802 // DemoTypeHookProc 803 // 804 //---------------------------------------------------------------------------- 805 LRESULT CALLBACK DemoTypeHookProc( int nCode, WPARAM wParam, LPARAM lParam ) 806 { 807 static HWND hWndFocus = nullptr; 808 static int injectionRatio = 1; 809 static int blockDepth = 0; 810 811 if( g_HookState == KILL_STATE ) 812 { 813 PostQuitMessage( 0 ); 814 return 1; 815 } 816 else if( g_HookState == START_STATE ) 817 { 818 g_HookState = INIT_STATE; 819 if( g_UserDriven ) 820 { 821 injectionRatio = min( 3, max( 1, static_cast<int>(g_SpeedSlider / THIRD_TYPING_SPEED) + 1 ) ); 822 } 823 824 hWndFocus = GetForegroundWindow(); 825 g_Notepad = IsWindowNotepad( hWndFocus ); 826 blockDepth = BlockModifierKeys(); 827 } 828 829 if( nCode == HC_ACTION ) 830 { 831 KBDLLHOOKSTRUCT* pKbdStruct = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam); 832 833 bool injected = IsInjected( pKbdStruct->vkCode, pKbdStruct->scanCode ); 834 835 // Block non-injected input until we've negated all modifiers 836 if( g_HookState == INIT_STATE ) 837 { 838 if( g_Injections.empty() ) 839 { 840 g_HookState = ACTIVE_STATE; 841 { 842 std::lock_guard<std::mutex> epochLock(g_EpochMutex); 843 } 844 g_EpochReady.notify_one(); 845 846 if( g_UserDriven ) 847 { 848 // Set baseline indentation to a blocking flag 849 g_BaselineIndentation = INDENT_SEEK_FLAG; 850 851 // Initialize the injection handler 852 HandleInjection( true ); 853 } 854 return 1; 855 } 856 } 857 else if( g_HookState == BLOCK_STATE ) 858 { 859 return 1; 860 } 861 862 // Handle two possible kill signals: user inputted escape or focus change 863 if( (pKbdStruct->vkCode == VK_ESCAPE && !injected) || hWndFocus != GetForegroundWindow() ) 864 { 865 // Notify the controller that the hook is going to BLOCK_STATE and requesting kill 866 g_HookState = BLOCK_STATE; 867 g_Kill = true; 868 { 869 std::lock_guard<std::mutex> epochLock(g_EpochMutex); 870 } 871 g_EpochReady.notify_one(); 872 873 // In user-driven mode, we can kill the hook without controller approval 874 // In standard mode, we need to stay alive until the emitter is terminated 875 if( g_UserDriven ) 876 { 877 PostQuitMessage( 0 ); 878 } 879 880 // Only pass through if the kill signal was user input after a focus change 881 if( hWndFocus != GetForegroundWindow() && injected ) 882 { 883 return 1; 884 } 885 } 886 else if( injected ) 887 { 888 PopInjection(); 889 } 890 else 891 { 892 switch( wParam ) 893 { 894 case WM_KEYUP: 895 // In user-driven mode, [end] needs to be acknowledged by the user with a space before proceeding to kill 896 if( pKbdStruct->vkCode == VK_SPACE && g_UserDriven && g_End ) 897 { 898 // Notify the controller that the hook is going to BLOCK_STATE and requesting kill 899 g_HookState = BLOCK_STATE; 900 g_Kill = true; 901 { 902 std::lock_guard<std::mutex> epochLock(g_EpochMutex); 903 } 904 g_EpochReady.notify_one(); 905 906 PostQuitMessage( 0 ); 907 } 908 else if( g_UserDriven ) 909 { 910 // Block up to the number of DEMOTYPE_HOTKEY keys 911 if( blockDepth > 0 ) 912 { 913 blockDepth--; 914 return 1; 915 } 916 else if( g_BaselineIndentation == INDENT_SEEK_FLAG ) 917 { 918 return 1; 919 } 920 921 // Inject n keys per 1 input keys where n is injectionRatio 922 for( int i = 0; i < injectionRatio && g_Index < g_TextLen && !g_End; i++ ) 923 { 924 HandleInjection(); 925 } 926 if( g_Index >= g_TextLen ) 927 { 928 g_Index = 0; 929 930 // Synthesize [end] at end of script if no [end] is present 931 if( !g_End ) 932 { 933 g_End = true; 934 g_TextSegments.push_back( g_Index ); 935 } 936 } 937 } 938 [[fallthrough]]; 939 case WM_KEYDOWN: 940 case WM_SYSKEYUP: 941 case WM_SYSKEYDOWN: 942 return 1; 943 } 944 } 945 } 946 return CallNextHookEx( g_hHook, nCode, wParam, lParam ); 947 } 948 949 //---------------------------------------------------------------------------- 950 // 951 // StartDemoTypeHook 952 // 953 //---------------------------------------------------------------------------- 954 void StartDemoTypeHook() 955 { 956 g_hHook = SetWindowsHookEx( WH_KEYBOARD_LL, DemoTypeHookProc, GetModuleHandle( nullptr ), 0 ); 957 if( g_hHook == nullptr ) 958 { 959 g_HookState = INACTIVE_STATE; 960 return; 961 } 962 963 // Jump start the hook with an inert message to prevent a stall 964 KBDLLHOOKSTRUCT KbdStruct{}; 965 DemoTypeHookProc( HC_ACTION, 0, reinterpret_cast<LPARAM>(&KbdStruct) ); 966 967 MSG msg; 968 while( GetMessage( &msg, nullptr, 0, 0 ) ) 969 { 970 TranslateMessage( &msg ); 971 DispatchMessage( &msg ); 972 } 973 974 UnhookWindowsHookEx( g_hHook ); 975 g_hHook = nullptr; 976 977 // Clean up any trailing shift modifier from our injections 978 if( (GetKeyState( VK_LSHIFT ) & 0x8000) != 0 ) 979 { 980 SendVirtualKeyUp( VK_LSHIFT ); 981 } 982 983 g_HookState = INACTIVE_STATE; 984 } 985 986 //---------------------------------------------------------------------------- 987 // 988 // KillDemoTypeHook 989 // 990 //---------------------------------------------------------------------------- 991 void KillDemoTypeHook() 992 { 993 if( g_HookState != INACTIVE_STATE ) 994 { 995 g_HookState = KILL_STATE; 996 SendVirtualKeyUp( VK_ESCAPE ); 997 } 998 } 999 1000 //---------------------------------------------------------------------------- 1001 // 1002 // DemoTypeController 1003 // 1004 //---------------------------------------------------------------------------- 1005 void DemoTypeController() 1006 { 1007 std::chrono::milliseconds timeout(DEMOTYPE_TIMEOUT); 1008 1009 g_Injections.clear(); 1010 g_End = false; 1011 g_Kill = false; 1012 g_Active = true; 1013 g_HookState = START_STATE; 1014 1015 std::thread( StartDemoTypeHook ).detach(); 1016 1017 // Spool up the emitter 1018 if( !g_UserDriven ) 1019 { 1020 std::unique_lock<std::mutex> epochLock(g_EpochMutex); 1021 if( g_EpochReady.wait_for( epochLock, timeout, 1022 [] { return g_HookState == ACTIVE_STATE; } ) ) 1023 { 1024 g_EmitterState = ACTIVE_STATE; 1025 std::thread( DemoTypeEmitter ).detach(); 1026 } 1027 else 1028 { 1029 KillDemoTypeHook(); 1030 g_Active = false; 1031 return; 1032 } 1033 } 1034 1035 // Wait for kill request 1036 { 1037 std::unique_lock<std::mutex> epochLock(g_EpochMutex); 1038 g_EpochReady.wait( epochLock, [] { return g_Kill == true; } ); 1039 } 1040 1041 // Send kill messages 1042 if( !g_UserDriven ) 1043 { 1044 if( g_EmitterState != INACTIVE_STATE ) 1045 { 1046 g_EmitterState = KILL_STATE; 1047 g_HookState = BLOCK_STATE; 1048 1049 std::unique_lock<std::mutex> epochLock(g_EpochMutex); 1050 g_EpochReady.wait_for( epochLock, timeout, [] { return g_EmitterState == INACTIVE_STATE; } ); 1051 } 1052 } 1053 KillDemoTypeHook(); 1054 1055 if( g_ClipboardCache != nullptr ) 1056 { 1057 SetClipboard( g_ClipboardCache ); 1058 g_LastClipboardSeq = GetClipboardSequence(); 1059 } 1060 1061 // Upon kill, hop to the next text segment if kill wasn't triggered by an [end] 1062 if( g_Index != 0 && !g_End ) 1063 { 1064 size_t nextEnd = g_Text.find( L"[end]", g_Index ); 1065 if( nextEnd == std::wstring::npos ) 1066 { 1067 g_Index = 0; 1068 } 1069 else 1070 { 1071 g_Index = nextEnd + END_CONTROL_LEN; 1072 g_TextSegments.push_back( g_Index ); 1073 if( g_Index >= g_TextLen ) 1074 { 1075 g_Index = 0; 1076 } 1077 } 1078 } 1079 1080 g_Active = false; 1081 } 1082 1083 //---------------------------------------------------------------------------- 1084 // 1085 // TrimNewlineAroundControl 1086 // 1087 //---------------------------------------------------------------------------- 1088 void TrimNewlineAroundControl( const std::wstring control, const bool trimLeft, const bool trimRight ) 1089 { 1090 const size_t controlLen = control.length(); 1091 1092 // Seek first occurrence of `control` in `g_Text` 1093 size_t nextControl = g_Text.find( control ); 1094 1095 // Loop through each occurrence of `control` in `g_Text` 1096 while( nextControl != std::wstring::npos ) 1097 { 1098 // Erase the character to the left of `control` if it is a newline 1099 if( trimLeft && nextControl > 0 && g_Text[nextControl - 1] == L'\n' ) 1100 { 1101 g_Text.erase( nextControl - 1, 1 ); 1102 // Decrement `nextControl` to account for `g_Text` shrinking to left of `nextControl` 1103 nextControl--; 1104 } 1105 1106 // Erase the character to the right of `control` if it is a newline 1107 if( trimRight && (nextControl + controlLen) < g_Text.length() && g_Text[nextControl + controlLen] == L'\n' ) 1108 { 1109 g_Text.erase( nextControl + controlLen, 1 ); 1110 } 1111 1112 // Seek next occurrence of `control` in `g_Text` 1113 nextControl = g_Text.find( control, nextControl + controlLen); 1114 1115 // Shrink `g_Text` to new size on last pass 1116 if( nextControl == std::wstring::npos ) 1117 { 1118 g_Text.shrink_to_fit(); 1119 } 1120 } 1121 } 1122 1123 //---------------------------------------------------------------------------- 1124 // 1125 // CleanDemoTypeText 1126 // 1127 //---------------------------------------------------------------------------- 1128 bool CleanDemoTypeText() 1129 { 1130 // Remove all unsupported characters from our text buffer 1131 g_Text.erase( std::remove_if( g_Text.begin(), g_Text.end(), IsNotPrintable ), g_Text.end() ); 1132 g_Text.shrink_to_fit(); 1133 1134 // Remove the first character if it is a newline 1135 if( g_Text.length() > 0 && g_Text[0] == L'\n' ) 1136 { 1137 g_Text.erase( 0, 1 ); 1138 g_Text.shrink_to_fit(); 1139 } 1140 1141 // Trim a newline character to the left and right of each [end] control 1142 TrimNewlineAroundControl( L"[end]", true, true ); 1143 1144 // Trim a newline character to the right of each [paste] control 1145 TrimNewlineAroundControl( L"[paste]", false, true ); 1146 1147 // Trim a newline character to the left of each [/paste] control 1148 TrimNewlineAroundControl( L"[/paste]", true, false ); 1149 1150 // Remove any dangling whitespace after the last [end] 1151 size_t lastEnd = g_Text.rfind( L"[end]" ); 1152 if( lastEnd != std::wstring::npos ) 1153 { 1154 for( size_t i = lastEnd + END_CONTROL_LEN; i < g_Text.length(); i++ ) 1155 { 1156 if( iswprint( g_Text[i] ) && g_Text[i] != L' ' ) 1157 { 1158 break; 1159 } 1160 else if( i >= g_Text.length() - 1 ) 1161 { 1162 g_Text.erase( lastEnd + END_CONTROL_LEN ); 1163 g_Text.shrink_to_fit(); 1164 } 1165 } 1166 } 1167 1168 g_TextLen = g_Text.length(); 1169 if( g_TextLen > 0 ) 1170 { 1171 return true; 1172 } 1173 else 1174 { 1175 return false; 1176 } 1177 } 1178 1179 //---------------------------------------------------------------------------- 1180 // 1181 // ResetDemoTypeClipboard 1182 // 1183 //---------------------------------------------------------------------------- 1184 void ResetDemoTypeClipboard() 1185 { 1186 if( g_Clipboard ) 1187 { 1188 g_Text.clear(); 1189 g_Clipboard = false; 1190 } 1191 } 1192 1193 //---------------------------------------------------------------------------- 1194 // 1195 // GetDemoTypeClipboard 1196 // 1197 //---------------------------------------------------------------------------- 1198 bool GetDemoTypeClipboard() 1199 { 1200 const int safetyPrefixLen = 7; 1201 const wchar_t safetyPrefix[] = L"[start]"; 1202 1203 // Check if we can reuse the clipboard cache 1204 DWORD sequenceNum = GetClipboardSequence(); 1205 if( g_LastClipboardSeq == sequenceNum && g_Clipboard ) 1206 { 1207 return true; 1208 } 1209 g_LastClipboardSeq = sequenceNum; 1210 1211 delete[] g_ClipboardCache; 1212 g_ClipboardCache = GetClipboard(); 1213 1214 // Confirm clipboard data begins with the safety prefix 1215 if( g_ClipboardCache == nullptr || g_ClipboardCache[0] != g_ClipboardCache[0] || g_ClipboardCache[safetyPrefixLen] == '\0' ) 1216 { 1217 ResetDemoTypeClipboard(); 1218 return false; 1219 } 1220 for( int i = 1; i < safetyPrefixLen; i++ ) 1221 { 1222 if( g_ClipboardCache[i] != safetyPrefix[i] || g_ClipboardCache[i] == '\0' ) 1223 { 1224 ResetDemoTypeClipboard(); 1225 return false; 1226 } 1227 } 1228 1229 g_Text.assign( g_ClipboardCache + safetyPrefixLen ); 1230 g_Clipboard = true; 1231 g_Index = 0; 1232 return CleanDemoTypeText(); 1233 } 1234 1235 //---------------------------------------------------------------------------- 1236 // 1237 // GetDemoTypeFile 1238 // 1239 // Supported encoding: UTF-8, UTF-8 with BOM, UTF-16LE, UTF-16BE 1240 // 1241 //---------------------------------------------------------------------------- 1242 int GetDemoTypeFile( const TCHAR* filePath ) 1243 { 1244 std::ifstream file(filePath, std::ios::binary); 1245 if( !file.is_open() ) 1246 { 1247 return ERROR_LOADING_FILE; 1248 } 1249 1250 // Confirm file size doesn't exceed MAX_INPUT_SIZE 1251 file.seekg( 0, std::ios::end ); 1252 std::streampos size = file.tellg(); 1253 file.seekg( 0, std::ios::beg ); 1254 if( size <= 0 || size > MAX_INPUT_SIZE ) 1255 { 1256 return FILE_SIZE_OVERFLOW; 1257 } 1258 1259 // Grab the potential Byte Order Mark 1260 // Which identifies the encoding pattern 1261 char byteOrderMark[3]; 1262 file.read( byteOrderMark, 3 ); 1263 file.seekg( 0, std::ios::beg ); 1264 1265 // UTF-16 is a variable-length character encoding pattern 1266 // - code points are encoded with one or two 16-bit code units 1267 // - 16-bit code units are composed of byte pairs subject to endianness 1268 // - Little-endian Byte Order Mark {0xFF, 0xFE, ...} 1269 // - Big-endian Byte Order Mark {0xFE, 0xFF, ...} 1270 1271 // UTF-8 is a variable-length character encoding pattern 1272 // - code points are encoded with one to four 8-bit code units 1273 // - optional Byte Order Mark {0xEF, 0xBB, 0xBF, ...} 1274 1275 // UTF-16LE 1276 if( byteOrderMark[0] == static_cast<char>(0xFF) 1277 && byteOrderMark[1] == static_cast<char>(0xFE) ) 1278 { 1279 // Truncate the Byte Order Mark 1280 file.seekg( 2 ); 1281 1282 char bytePair[2]; 1283 wchar_t codeUnit; 1284 while( file.read( bytePair, 2 ) ) 1285 { 1286 // Squash each little-endian byte pair into a 2-byte code unit 1287 // if bytePair[0] = 0xff 1288 // if bytePair[1] = 0x00 1289 // codeUnit = 0x00ff 1290 codeUnit = (static_cast<wchar_t>(bytePair[1]) << 8) 1291 | static_cast<wchar_t>(bytePair[0]); 1292 1293 g_Text += codeUnit; 1294 } 1295 } 1296 // UTF-16BE 1297 else if( byteOrderMark[0] == static_cast<char>(0xFE) 1298 && byteOrderMark[1] == static_cast<char>(0xFF) ) 1299 { 1300 // Truncate the Byte Order Mark 1301 file.seekg( 2 ); 1302 1303 char bytePair[2]; 1304 wchar_t codeUnit; 1305 while( file.read( bytePair, 2 ) ) 1306 { 1307 // Squash each big-endian byte pair into a 2-byte code unit 1308 // if bytePair[0] = 0xff 1309 // if bytePair[1] = 0x00 1310 // codeUnit = 0xff00 1311 codeUnit = (static_cast<wchar_t>(bytePair[0]) << 8) 1312 | static_cast<wchar_t>(bytePair[1]); 1313 1314 g_Text += codeUnit; 1315 } 1316 } 1317 // UTF-8 1318 else 1319 { 1320 // If UTF-8 with BOM, truncate the Byte Order Mark 1321 if( byteOrderMark[0] == static_cast<char>(0xEF) 1322 && byteOrderMark[1] == static_cast<char>(0xBB) 1323 && byteOrderMark[2] == static_cast<char>(0xBF) ) 1324 { 1325 file.seekg( 3 ); 1326 } 1327 1328 std::stringstream buffer; 1329 buffer << file.rdbuf(); 1330 std::string narrowText = buffer.str(); 1331 1332 // Determine the size our wide string will need to be to accomodate the conversion 1333 int wideSize = MultiByteToWideChar( CP_UTF8, 0, narrowText.c_str(), -1, nullptr, 0 ); 1334 if( wideSize <= 0 ) 1335 { 1336 return ERROR_LOADING_FILE; 1337 } 1338 1339 g_Text.resize( wideSize ); 1340 1341 // Map the multi-byte capable char string onto the wide char string 1342 if( MultiByteToWideChar( CP_UTF8, 0, narrowText.c_str(), -1, &g_Text[0], wideSize ) <= 0 ) 1343 { 1344 return ERROR_LOADING_FILE; 1345 } 1346 } 1347 1348 g_Index = 0; 1349 return CleanDemoTypeText() ? 0 : UNKNOWN_FILE_DATA; 1350 } 1351 1352 //---------------------------------------------------------------------------- 1353 // 1354 // ResetDemoTypeIndex 1355 // 1356 //---------------------------------------------------------------------------- 1357 void ResetDemoTypeIndex() 1358 { 1359 size_t newIndex = 0; 1360 1361 if( !g_TextSegments.empty() && g_Index <= g_TextSegments.back() ) 1362 { 1363 g_TextSegments.pop_back(); 1364 } 1365 if( !g_TextSegments.empty() ) 1366 { 1367 newIndex = g_TextSegments.back(); 1368 } 1369 1370 g_Index = newIndex; 1371 } 1372 1373 //---------------------------------------------------------------------------- 1374 // 1375 // StartDemoType 1376 // 1377 //---------------------------------------------------------------------------- 1378 int StartDemoType( const TCHAR* filePath, const DWORD speedSlider, const BOOLEAN userDriven ) 1379 { 1380 static FILETIME lastFileWrite = {0}; 1381 1382 if( g_Active ) 1383 { 1384 return -1; 1385 } 1386 1387 if( !GetDemoTypeClipboard() ) 1388 { 1389 if( _tcslen( filePath ) == 0 ) 1390 { 1391 return NO_FILE_SPECIFIED; 1392 } 1393 1394 if( _tcscmp( g_LastFilePath, filePath ) != 0 ) 1395 { 1396 _tcscpy( g_LastFilePath, filePath ); 1397 // Trigger (re)capture of lastFileWrite 1398 g_Text = L"x"; 1399 g_Index = 0; 1400 lastFileWrite = {0}; 1401 } 1402 1403 // Check if the file has been updated since last read 1404 if( !g_Text.empty() ) 1405 { 1406 HANDLE hFile = CreateFile( filePath, GENERIC_READ, FILE_SHARE_READ, 1407 nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr ); 1408 if( hFile == INVALID_HANDLE_VALUE ) 1409 { 1410 CloseHandle( hFile ); 1411 return ERROR_LOADING_FILE; 1412 } 1413 1414 FILETIME latestFileWrite; 1415 if( GetFileTime( hFile, nullptr, nullptr, &latestFileWrite ) ) 1416 { 1417 if( CompareFileTime( &latestFileWrite, &lastFileWrite ) == 1 ) 1418 { 1419 g_Text.clear(); 1420 lastFileWrite = latestFileWrite; 1421 } 1422 } 1423 CloseHandle( hFile ); 1424 } 1425 1426 if( g_Text.empty() ) 1427 { 1428 switch( GetDemoTypeFile( filePath ) ) 1429 { 1430 case ERROR_LOADING_FILE: 1431 return ERROR_LOADING_FILE; 1432 1433 case FILE_SIZE_OVERFLOW: 1434 return FILE_SIZE_OVERFLOW; 1435 1436 case UNKNOWN_FILE_DATA: 1437 return UNKNOWN_FILE_DATA; 1438 } 1439 } 1440 } 1441 1442 g_UserDriven = userDriven; 1443 g_SpeedSlider = speedSlider; 1444 std::thread( DemoTypeController ).detach(); 1445 return 0; 1446 }