1616 import  msvcrt 
1717 from  ctypes  import  windll 
1818
19- from  ctypes  import  Array , pointer 
19+ from  ctypes  import  Array , byref ,  pointer 
2020from  ctypes .wintypes  import  DWORD , HANDLE 
2121from  typing  import  Callable , ContextManager , Iterable , Iterator , TextIO 
2222
3535
3636from  .ansi_escape_sequences  import  REVERSE_ANSI_SEQUENCES 
3737from  .base  import  Input 
38+ from  .vt100_parser  import  Vt100Parser 
3839
3940__all__  =  [
4041 "Win32Input" ,
5253MOUSE_MOVED  =  0x0001 
5354MOUSE_WHEELED  =  0x0004 
5455
56+ # See: https://msdn.microsoft.com/pl-pl/library/windows/desktop/ms686033(v=vs.85).aspx 
57+ ENABLE_VIRTUAL_TERMINAL_INPUT  =  0x0200 
58+ 
5559
5660class  _Win32InputBase (Input ):
5761 """ 
@@ -74,7 +78,14 @@ class Win32Input(_Win32InputBase):
7478
7579 def  __init__ (self , stdin : TextIO  |  None  =  None ) ->  None :
7680 super ().__init__ ()
77-  self .console_input_reader  =  ConsoleInputReader ()
81+  self ._use_virtual_terminal_input  =  _is_win_vt100_input_enabled ()
82+ 
83+  self .console_input_reader : Vt100ConsoleInputReader  |  ConsoleInputReader 
84+ 
85+  if  self ._use_virtual_terminal_input :
86+  self .console_input_reader  =  Vt100ConsoleInputReader ()
87+  else :
88+  self .console_input_reader  =  ConsoleInputReader ()
7889
7990 def  attach (self , input_ready_callback : Callable [[], None ]) ->  ContextManager [None ]:
8091 """ 
@@ -101,7 +112,9 @@ def closed(self) -> bool:
101112 return  False 
102113
103114 def  raw_mode (self ) ->  ContextManager [None ]:
104-  return  raw_mode ()
115+  return  raw_mode (
116+  use_win10_virtual_terminal_input = self ._use_virtual_terminal_input 
117+  )
105118
106119 def  cooked_mode (self ) ->  ContextManager [None ]:
107120 return  cooked_mode ()
@@ -555,6 +568,102 @@ def _handle_mouse(self, ev: MOUSE_EVENT_RECORD) -> list[KeyPress]:
555568 return  [KeyPress (Keys .WindowsMouseEvent , data )]
556569
557570
571+ class  Vt100ConsoleInputReader :
572+  """ 
573+  Similar to `ConsoleInputReader`, but for usage when 
574+  `ENABLE_VIRTUAL_TERMINAL_INPUT` is enabled. This assumes that Windows sends 
575+  us the right vt100 escape sequences and we parse those with our vt100 
576+  parser. 
577+ 
578+  (Using this instead of `ConsoleInputReader` results in the "data" attribute 
579+  from the `KeyPress` instances to be more correct in edge cases, because 
580+  this responds to for instance the terminal being in application cursor keys 
581+  mode.) 
582+  """ 
583+ 
584+  def  __init__ (self ) ->  None :
585+  self ._fdcon  =  None 
586+ 
587+  self ._buffer : list [KeyPress ] =  [] # Buffer to collect the Key objects. 
588+  self ._vt100_parser  =  Vt100Parser (
589+  lambda  key_press : self ._buffer .append (key_press )
590+  )
591+ 
592+  # When stdin is a tty, use that handle, otherwise, create a handle from 
593+  # CONIN$. 
594+  self .handle : HANDLE 
595+  if  sys .stdin .isatty ():
596+  self .handle  =  HANDLE (windll .kernel32 .GetStdHandle (STD_INPUT_HANDLE ))
597+  else :
598+  self ._fdcon  =  os .open ("CONIN$" , os .O_RDWR  |  os .O_BINARY )
599+  self .handle  =  HANDLE (msvcrt .get_osfhandle (self ._fdcon ))
600+ 
601+  def  close (self ) ->  None :
602+  "Close fdcon." 
603+  if  self ._fdcon  is  not None :
604+  os .close (self ._fdcon )
605+ 
606+  def  read (self ) ->  Iterable [KeyPress ]:
607+  """ 
608+  Return a list of `KeyPress` instances. It won't return anything when 
609+  there was nothing to read. (This function doesn't block.) 
610+ 
611+  http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx 
612+  """ 
613+  max_count  =  2048  # Max events to read at the same time. 
614+ 
615+  read  =  DWORD (0 )
616+  arrtype  =  INPUT_RECORD  *  max_count 
617+  input_records  =  arrtype ()
618+ 
619+  # Check whether there is some input to read. `ReadConsoleInputW` would 
620+  # block otherwise. 
621+  # (Actually, the event loop is responsible to make sure that this 
622+  # function is only called when there is something to read, but for some 
623+  # reason this happened in the asyncio_win32 loop, and it's better to be 
624+  # safe anyway.) 
625+  if  not  wait_for_handles ([self .handle ], timeout = 0 ):
626+  return  []
627+ 
628+  # Get next batch of input event. 
629+  windll .kernel32 .ReadConsoleInputW (
630+  self .handle , pointer (input_records ), max_count , pointer (read )
631+  )
632+ 
633+  # First, get all the keys from the input buffer, in order to determine 
634+  # whether we should consider this a paste event or not. 
635+  for  key_data  in  self ._get_keys (read , input_records ):
636+  self ._vt100_parser .feed (key_data )
637+ 
638+  # Return result. 
639+  result  =  self ._buffer 
640+  self ._buffer  =  []
641+  return  result 
642+ 
643+  def  _get_keys (
644+  self , read : DWORD , input_records : Array [INPUT_RECORD ]
645+  ) ->  Iterator [str ]:
646+  """ 
647+  Generator that yields `KeyPress` objects from the input records. 
648+  """ 
649+  for  i  in  range (read .value ):
650+  ir  =  input_records [i ]
651+ 
652+  # Get the right EventType from the EVENT_RECORD. 
653+  # (For some reason the Windows console application 'cmder' 
654+  # [http://gooseberrycreative.com/cmder/] can return '0' for 
655+  # ir.EventType. -- Just ignore that.) 
656+  if  ir .EventType  in  EventTypes :
657+  ev  =  getattr (ir .Event , EventTypes [ir .EventType ])
658+ 
659+  # Process if this is a key event. (We also have mouse, menu and 
660+  # focus events.) 
661+  if  isinstance (ev , KEY_EVENT_RECORD ) and  ev .KeyDown :
662+  u_char  =  ev .uChar .UnicodeChar 
663+  if  u_char  !=  "\x00 " :
664+  yield  u_char 
665+ 
666+ 
558667class  _Win32Handles :
559668 """ 
560669 Utility to keep track of which handles are connectod to which callbacks. 
@@ -700,8 +809,11 @@ class raw_mode:
700809 `raw_input` method of `.vt100_input`. 
701810 """ 
702811
703-  def  __init__ (self , fileno : int  |  None  =  None ) ->  None :
812+  def  __init__ (
813+  self , fileno : int  |  None  =  None , use_win10_virtual_terminal_input : bool  =  False 
814+  ) ->  None :
704815 self .handle  =  HANDLE (windll .kernel32 .GetStdHandle (STD_INPUT_HANDLE ))
816+  self .use_win10_virtual_terminal_input  =  use_win10_virtual_terminal_input 
705817
706818 def  __enter__ (self ) ->  None :
707819 # Remember original mode. 
@@ -717,12 +829,15 @@ def _patch(self) -> None:
717829 ENABLE_LINE_INPUT  =  0x0002 
718830 ENABLE_PROCESSED_INPUT  =  0x0001 
719831
720-  windll .kernel32 .SetConsoleMode (
721-  self .handle ,
722-  self .original_mode .value 
723-  &  ~ (ENABLE_ECHO_INPUT  |  ENABLE_LINE_INPUT  |  ENABLE_PROCESSED_INPUT ),
832+  new_mode  =  self .original_mode .value  &  ~ (
833+  ENABLE_ECHO_INPUT  |  ENABLE_LINE_INPUT  |  ENABLE_PROCESSED_INPUT 
724834 )
725835
836+  if  self .use_win10_virtual_terminal_input :
837+  new_mode  |=  ENABLE_VIRTUAL_TERMINAL_INPUT 
838+ 
839+  windll .kernel32 .SetConsoleMode (self .handle , new_mode )
840+ 
726841 def  __exit__ (self , * a : object ) ->  None :
727842 # Restore original mode 
728843 windll .kernel32 .SetConsoleMode (self .handle , self .original_mode )
@@ -747,3 +862,25 @@ def _patch(self) -> None:
747862 self .original_mode .value 
748863 |  (ENABLE_ECHO_INPUT  |  ENABLE_LINE_INPUT  |  ENABLE_PROCESSED_INPUT ),
749864 )
865+ 
866+ 
867+ def  _is_win_vt100_input_enabled () ->  bool :
868+  """ 
869+  Returns True when we're running Windows and VT100 escape sequences are 
870+  supported. 
871+  """ 
872+  hconsole  =  HANDLE (windll .kernel32 .GetStdHandle (STD_INPUT_HANDLE ))
873+ 
874+  # Get original console mode. 
875+  original_mode  =  DWORD (0 )
876+  windll .kernel32 .GetConsoleMode (hconsole , byref (original_mode ))
877+ 
878+  try :
879+  # Try to enable VT100 sequences. 
880+  result : int  =  windll .kernel32 .SetConsoleMode (
881+  hconsole , DWORD (ENABLE_VIRTUAL_TERMINAL_INPUT )
882+  )
883+ 
884+  return  result  ==  1 
885+  finally :
886+  windll .kernel32 .SetConsoleMode (hconsole , original_mode )
0 commit comments