xref: /libuv/test/test-tty-duplicate-key.c (revision d8669609)
1 /* Copyright libuv project contributors. All rights reserved.
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining a copy
4  * of this software and associated documentation files (the "Software"), to
5  * deal in the Software without restriction, including without limitation the
6  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7  * sell copies of the Software, and to permit persons to whom the Software is
8  * furnished to do so, subject to the following conditions:
9  *
10  * The above copyright notice and this permission notice shall be included in
11  * all copies or substantial portions of the Software.
12  *
13  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19  * IN THE SOFTWARE.
20  */
21 
22 #ifdef _WIN32
23 
24 #include "uv.h"
25 #include "task.h"
26 
27 #include <errno.h>
28 #include <io.h>
29 #include <string.h>
30 #include <windows.h>
31 
32 #define ESC "\x1b"
33 #define EUR_UTF8 "\xe2\x82\xac"
34 #define EUR_UNICODE 0x20AC
35 
36 
37 const char* expect_str = NULL;
38 ssize_t expect_nread = 0;
39 
dump_str(const char * str,ssize_t len)40 static void dump_str(const char* str, ssize_t len) {
41   ssize_t i;
42   for (i = 0; i < len; i++) {
43     fprintf(stderr, "%#02x ", *(str + i));
44   }
45 }
46 
print_err_msg(const char * expect,ssize_t expect_len,const char * found,ssize_t found_len)47 static void print_err_msg(const char* expect, ssize_t expect_len,
48                           const char* found, ssize_t found_len) {
49   fprintf(stderr, "expect ");
50   dump_str(expect, expect_len);
51   fprintf(stderr, ", but found ");
52   dump_str(found, found_len);
53   fprintf(stderr, "\n");
54 }
55 
tty_alloc(uv_handle_t * handle,size_t size,uv_buf_t * buf)56 static void tty_alloc(uv_handle_t* handle, size_t size, uv_buf_t* buf) {
57   buf->base = malloc(size);
58   ASSERT_NOT_NULL(buf->base);
59   buf->len = size;
60 }
61 
tty_read(uv_stream_t * tty_in,ssize_t nread,const uv_buf_t * buf)62 static void tty_read(uv_stream_t* tty_in, ssize_t nread, const uv_buf_t* buf) {
63   if (nread > 0) {
64     if (nread != expect_nread) {
65       fprintf(stderr, "expected nread %ld, but found %ld\n",
66               (long)expect_nread, (long)nread);
67       print_err_msg(expect_str, expect_nread, buf->base, nread);
68       ASSERT(FALSE);
69     }
70     if (strncmp(buf->base, expect_str, nread) != 0) {
71       print_err_msg(expect_str, expect_nread, buf->base, nread);
72       ASSERT(FALSE);
73     }
74     uv_close((uv_handle_t*) tty_in, NULL);
75   } else {
76     ASSERT_OK(nread);
77   }
78 }
79 
make_key_event_records(WORD virt_key,DWORD ctr_key_state,BOOL is_wsl,INPUT_RECORD * records)80 static void make_key_event_records(WORD virt_key, DWORD ctr_key_state,
81                                    BOOL is_wsl, INPUT_RECORD* records) {
82 # define KEV(I) records[(I)].Event.KeyEvent
83   BYTE kb_state[256] = {0};
84   WCHAR buf[2];
85   int ret;
86 
87   records[0].EventType = records[1].EventType = KEY_EVENT;
88   KEV(0).bKeyDown = TRUE;
89   KEV(1).bKeyDown = FALSE;
90   KEV(0).wVirtualKeyCode = KEV(1).wVirtualKeyCode = virt_key;
91   KEV(0).wRepeatCount = KEV(1).wRepeatCount = 1;
92   KEV(0).wVirtualScanCode = KEV(1).wVirtualScanCode =
93     MapVirtualKeyW(virt_key, MAPVK_VK_TO_VSC);
94   KEV(0).dwControlKeyState = KEV(1).dwControlKeyState = ctr_key_state;
95   if (ctr_key_state & LEFT_ALT_PRESSED) {
96     kb_state[VK_LMENU] = 0x01;
97   }
98   if (ctr_key_state & RIGHT_ALT_PRESSED) {
99     kb_state[VK_RMENU] = 0x01;
100   }
101   if (ctr_key_state & LEFT_CTRL_PRESSED) {
102     kb_state[VK_LCONTROL] = 0x01;
103   }
104   if (ctr_key_state & RIGHT_CTRL_PRESSED) {
105     kb_state[VK_RCONTROL] = 0x01;
106   }
107   if (ctr_key_state & SHIFT_PRESSED) {
108     kb_state[VK_SHIFT] = 0x01;
109   }
110   ret = ToUnicode(virt_key, KEV(0).wVirtualScanCode, kb_state, buf, 2, 0);
111   if (ret == 1) {
112     if(!is_wsl &&
113         ((ctr_key_state & LEFT_ALT_PRESSED) ||
114          (ctr_key_state & RIGHT_ALT_PRESSED))) {
115       /*
116        * If ALT key is pressed, the UnicodeChar value of the keyup event is
117        * set to 0 on nomal console. Emulate this behavior.
118        * See https://github.com/Microsoft/console/issues/320
119        */
120       KEV(0).uChar.UnicodeChar = buf[0];
121       KEV(1).uChar.UnicodeChar = 0;
122     } else{
123       /*
124        * In WSL UnicodeChar is normally set. This behavior cause #2111.
125        */
126       KEV(0).uChar.UnicodeChar = KEV(1).uChar.UnicodeChar = buf[0];
127     }
128   } else {
129     KEV(0).uChar.UnicodeChar = KEV(1).uChar.UnicodeChar = 0;
130   }
131 # undef KEV
132 }
133 
TEST_IMPL(tty_duplicate_vt100_fn_key)134 TEST_IMPL(tty_duplicate_vt100_fn_key) {
135   int r;
136   int ttyin_fd;
137   uv_tty_t tty_in;
138   uv_loop_t* loop;
139   HANDLE handle;
140   INPUT_RECORD records[2];
141   DWORD written;
142 
143   loop = uv_default_loop();
144 
145   /* Make sure we have an FD that refers to a tty */
146   handle = CreateFileA("conin$",
147                        GENERIC_READ | GENERIC_WRITE,
148                        FILE_SHARE_READ | FILE_SHARE_WRITE,
149                        NULL,
150                        OPEN_EXISTING,
151                        FILE_ATTRIBUTE_NORMAL,
152                        NULL);
153   ASSERT_PTR_NE(handle, INVALID_HANDLE_VALUE);
154   ttyin_fd = _open_osfhandle((intptr_t) handle, 0);
155   ASSERT_GE(ttyin_fd, 0);
156   ASSERT_EQ(UV_TTY, uv_guess_handle(ttyin_fd));
157 
158   r = uv_tty_init(uv_default_loop(), &tty_in, ttyin_fd, 1);  /* Readable. */
159   ASSERT_OK(r);
160   ASSERT(uv_is_readable((uv_stream_t*) &tty_in));
161   ASSERT(!uv_is_writable((uv_stream_t*) &tty_in));
162 
163   r = uv_read_start((uv_stream_t*)&tty_in, tty_alloc, tty_read);
164   ASSERT_OK(r);
165 
166   expect_str = ESC"[[A";
167   expect_nread = strlen(expect_str);
168 
169   /* Turn on raw mode. */
170   r = uv_tty_set_mode(&tty_in, UV_TTY_MODE_RAW);
171   ASSERT_OK(r);
172 
173   /*
174    * Send F1 keystrokes. Test of issue cause by #2114 that vt100 fn key
175    * duplicate.
176    */
177   make_key_event_records(VK_F1, 0, TRUE, records);
178   WriteConsoleInputW(handle, records, ARRAY_SIZE(records), &written);
179   ASSERT_EQ(written, ARRAY_SIZE(records));
180 
181   uv_run(loop, UV_RUN_DEFAULT);
182 
183   MAKE_VALGRIND_HAPPY(loop);
184   return 0;
185 }
186 
TEST_IMPL(tty_duplicate_alt_modifier_key)187 TEST_IMPL(tty_duplicate_alt_modifier_key) {
188   int r;
189   int ttyin_fd;
190   uv_tty_t tty_in;
191   uv_loop_t* loop;
192   HANDLE handle;
193   INPUT_RECORD records[2];
194   INPUT_RECORD alt_records[2];
195   DWORD written;
196 
197   loop = uv_default_loop();
198 
199   /* Make sure we have an FD that refers to a tty */
200   handle = CreateFileA("conin$",
201                        GENERIC_READ | GENERIC_WRITE,
202                        FILE_SHARE_READ | FILE_SHARE_WRITE,
203                        NULL,
204                        OPEN_EXISTING,
205                        FILE_ATTRIBUTE_NORMAL,
206                        NULL);
207   ASSERT_PTR_NE(handle, INVALID_HANDLE_VALUE);
208   ttyin_fd = _open_osfhandle((intptr_t) handle, 0);
209   ASSERT_GE(ttyin_fd, 0);
210   ASSERT_EQ(UV_TTY, uv_guess_handle(ttyin_fd));
211 
212   r = uv_tty_init(uv_default_loop(), &tty_in, ttyin_fd, 1);  /* Readable. */
213   ASSERT_OK(r);
214   ASSERT(uv_is_readable((uv_stream_t*) &tty_in));
215   ASSERT(!uv_is_writable((uv_stream_t*) &tty_in));
216 
217   r = uv_read_start((uv_stream_t*)&tty_in, tty_alloc, tty_read);
218   ASSERT_OK(r);
219 
220   expect_str = ESC"a"ESC"a";
221   expect_nread = strlen(expect_str);
222 
223   /* Turn on raw mode. */
224   r = uv_tty_set_mode(&tty_in, UV_TTY_MODE_RAW);
225   ASSERT_OK(r);
226 
227   /* Emulate transmission of M-a at normal console */
228   make_key_event_records(VK_MENU, 0, TRUE, alt_records);
229   WriteConsoleInputW(handle, &alt_records[0], 1, &written);
230   ASSERT_EQ(1, written);
231   make_key_event_records(L'A', LEFT_ALT_PRESSED, FALSE, records);
232   WriteConsoleInputW(handle, records, ARRAY_SIZE(records), &written);
233   ASSERT_EQ(2, written);
234   WriteConsoleInputW(handle, &alt_records[1], 1, &written);
235   ASSERT_EQ(1, written);
236 
237   /* Emulate transmission of M-a at WSL(#2111) */
238   make_key_event_records(VK_MENU, 0, TRUE, alt_records);
239   WriteConsoleInputW(handle, &alt_records[0], 1, &written);
240   ASSERT_EQ(1, written);
241   make_key_event_records(L'A', LEFT_ALT_PRESSED, TRUE, records);
242   WriteConsoleInputW(handle, records, ARRAY_SIZE(records), &written);
243   ASSERT_EQ(2, written);
244   WriteConsoleInputW(handle, &alt_records[1], 1, &written);
245   ASSERT_EQ(1, written);
246 
247   uv_run(loop, UV_RUN_DEFAULT);
248 
249   MAKE_VALGRIND_HAPPY(loop);
250   return 0;
251 }
252 
TEST_IMPL(tty_composing_character)253 TEST_IMPL(tty_composing_character) {
254   int r;
255   int ttyin_fd;
256   uv_tty_t tty_in;
257   uv_loop_t* loop;
258   HANDLE handle;
259   INPUT_RECORD records[2];
260   INPUT_RECORD alt_records[2];
261   DWORD written;
262 
263   loop = uv_default_loop();
264 
265   /* Make sure we have an FD that refers to a tty */
266   handle = CreateFileA("conin$",
267                        GENERIC_READ | GENERIC_WRITE,
268                        FILE_SHARE_READ | FILE_SHARE_WRITE,
269                        NULL,
270                        OPEN_EXISTING,
271                        FILE_ATTRIBUTE_NORMAL,
272                        NULL);
273   ASSERT_PTR_NE(handle, INVALID_HANDLE_VALUE);
274   ttyin_fd = _open_osfhandle((intptr_t) handle, 0);
275   ASSERT_GE(ttyin_fd, 0);
276   ASSERT_EQ(UV_TTY, uv_guess_handle(ttyin_fd));
277 
278   r = uv_tty_init(uv_default_loop(), &tty_in, ttyin_fd, 1);  /* Readable. */
279   ASSERT_OK(r);
280   ASSERT(uv_is_readable((uv_stream_t*) &tty_in));
281   ASSERT(!uv_is_writable((uv_stream_t*) &tty_in));
282 
283   r = uv_read_start((uv_stream_t*)&tty_in, tty_alloc, tty_read);
284   ASSERT_OK(r);
285 
286   expect_str = EUR_UTF8;
287   expect_nread = strlen(expect_str);
288 
289   /* Turn on raw mode. */
290   r = uv_tty_set_mode(&tty_in, UV_TTY_MODE_RAW);
291   ASSERT_OK(r);
292 
293   /* Emulate EUR inputs by LEFT ALT+NUMPAD ASCII KeyComos */
294   make_key_event_records(VK_MENU, 0, FALSE, alt_records);
295   alt_records[1].Event.KeyEvent.uChar.UnicodeChar = EUR_UNICODE;
296   WriteConsoleInputW(handle, &alt_records[0], 1, &written);
297   make_key_event_records(VK_NUMPAD0, LEFT_ALT_PRESSED, FALSE, records);
298   WriteConsoleInputW(handle, records, ARRAY_SIZE(records), &written);
299   ASSERT_EQ(written, ARRAY_SIZE(records));
300   make_key_event_records(VK_NUMPAD1, LEFT_ALT_PRESSED, FALSE, records);
301   WriteConsoleInputW(handle, records, ARRAY_SIZE(records), &written);
302   ASSERT_EQ(written, ARRAY_SIZE(records));
303   make_key_event_records(VK_NUMPAD2, LEFT_ALT_PRESSED, FALSE, records);
304   WriteConsoleInputW(handle, records, ARRAY_SIZE(records), &written);
305   ASSERT_EQ(written, ARRAY_SIZE(records));
306   make_key_event_records(VK_NUMPAD8, LEFT_ALT_PRESSED, FALSE, records);
307   WriteConsoleInputW(handle, records, ARRAY_SIZE(records), &written);
308   ASSERT_EQ(written, ARRAY_SIZE(records));
309   WriteConsoleInputW(handle, &alt_records[1], 1, &written);
310 
311   uv_run(loop, UV_RUN_DEFAULT);
312 
313   MAKE_VALGRIND_HAPPY(loop);
314   return 0;
315 }
316 
317 #else
318 
319 typedef int file_has_no_tests;  /* ISO C forbids an empty translation unit. */
320 
321 #endif  /* ifndef _WIN32 */
322