@@ -124,11 +124,84 @@ GetModifierKey(DWORD dwControlKeyState)
124124 return modKey ;
125125}
126126
127+ // ReadConsoleForTermEmul() but for ENABLE_VIRTUAL_TERMINAL_INPUT.
128+ static int
129+ ReadConsoleForTermEmulModern (HANDLE hInput , char * destin , int destinlen )
130+ {
131+ // If the previous input ended on a lead (high) surrogate,
132+ // we stash it here to combine it with the next input.
133+ static wchar_t s_previous_lead ;
134+
135+ INPUT_RECORD records [TERM_IO_BUF_SIZE_UTF16 ];
136+ DWORD records_cap = ARRAYSIZE (records );
137+ DWORD records_len = 0 ;
138+ wchar_t text [TERM_IO_BUF_SIZE_UTF16 ];
139+ int text_len = 0 ;
140+
141+ // If we'll restore the previous lead surrogate, we can only read
142+ // ARRAYSIZE(records)-1 records before the storage overflows.
143+ if (s_previous_lead ) {
144+ records_cap -- ;
145+ }
146+
147+ // As this application heavily relies on APCs, it's important that we call
148+ // DataAvailable(), because it calls WaitForSingleObjectEx with bAlertable=TRUE.
149+ if (!DataAvailable (hInput ) ||
150+ !ReadConsoleInputW (hInput , records , records_cap , & records_len ) ||
151+ records_len == 0 )
152+ return 0 ;
153+
154+ // Restore the previous lead surrogate if we have one.
155+ if (s_previous_lead ) {
156+ text [text_len ++ ] = s_previous_lead ;
157+ s_previous_lead = 0 ;
158+ }
159+
160+ // Accumulate the UTF-16 text.
161+ for (DWORD i = 0 ; i < records_len ; i ++ ) {
162+ switch (records [i ].EventType ) {
163+ case WINDOW_BUFFER_SIZE_EVENT :
164+ queue_terminal_window_change_event ();
165+ break ;
166+ case KEY_EVENT : {
167+ const KEY_EVENT_RECORD * k = & records [i ].Event .KeyEvent ;
168+ if (
169+ // The old Windows console added support for Unicode by encoding the characters in the
170+ // current code page as usual, while stuffing a UCS2 value into a trailing VK_MENU event.
171+ // Modern terminals on Windows stopped doing this and the Windows console may as well at some point.
172+ (k -> bKeyDown || k -> wVirtualKeyCode == VK_MENU ) &&
173+ // Current versions of ConPTY suffer from a bug where pressing modifier keys enqueues
174+ // a KEY_EVENT with UnicodeChar=0 despite ENABLE_VIRTUAL_TERMINAL_INPUT being enabled.
175+ // They can be identified by the fact that their UnicodeChar value is zero,
176+ // but they still have a non-zero wVirtualScanCode.
177+ (k -> uChar .UnicodeChar != L'\0' || k -> wVirtualScanCode == 0 ))
178+ text [text_len ++ ] = k -> uChar .UnicodeChar ;
179+ break ;
180+ }
181+ default :
182+ break ;
183+ }
184+ }
185+
186+ // Pop any lone lead surrogate from the input for later.
187+ const wchar_t last_char = text [text_len - 1 ];
188+ if (IS_HIGH_SURROGATE (last_char )) {
189+ s_previous_lead = last_char ;
190+ text_len -- ;
191+ }
192+
193+ // ...and finally convert everything to UTF-8.
194+ // It'll always fit, because we sized TERM_IO_BUF_SIZE to be large enough.
195+ return WideCharToMultiByte (CP_UTF8 , 0 , text , text_len , destin , destinlen , NULL , NULL );
196+ }
197+
127198int
128199ReadConsoleForTermEmul (HANDLE hInput , char * destin , int destinlen )
129200{
130- HANDLE hHandle [] = { hInput , NULL };
131- DWORD nHandle = 1 ;
201+ if (isConsoleVTSeqAvailable ) {
202+ return ReadConsoleForTermEmulModern (hInput , destin , destinlen );
203+ }
204+
132205 DWORD dwInput = 0 ;
133206 DWORD rc = 0 ;
134207 unsigned char octets [20 ];
@@ -187,23 +260,7 @@ ReadConsoleForTermEmul(HANDLE hInput, char *destin, int destinlen)
187260 break ;
188261 }
189262
190- if (isConsoleVTSeqAvailable ) {
191- if (inputRecord .Event .KeyEvent .uChar .UnicodeChar != L'\0' || inputRecord .Event .KeyEvent .wVirtualScanCode == 0 ) {
192- n = WideCharToMultiByte (
193- CP_UTF8 ,
194- 0 ,
195- & (inputRecord .Event .KeyEvent .uChar .UnicodeChar ),
196- 1 ,
197- (LPSTR )octets ,
198- 20 ,
199- NULL ,
200- NULL );
201-
202- WriteToBuffer ((char * )octets , n );
203- }
204- } else {
205- GetVTSeqFromKeyStroke (inputRecord );
206- }
263+ GetVTSeqFromKeyStroke (inputRecord );
207264 }
208265 break ;
209266 }
0 commit comments