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