Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34k
GH-132439: Fix REPL swallowing characters entered with AltGr on cmd.exe#132440
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
59d71b0cca61551e61540c60a727ed035e569e87798ee232ce67452b396f48e40b339a1fbe2402d59563adb89baFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -24,6 +24,7 @@ | ||
| MOVE_DOWN, | ||
| ERASE_IN_LINE, | ||
| ) | ||
| import _pyrepl.windows_console as wc | ||
| except ImportError: | ||
| pass | ||
| @@ -350,8 +351,226 @@ def test_multiline_ctrl_z(self): | ||
| Event(evt="key", data='\x1a', raw=bytearray(b'\x1a')), | ||
| ], | ||
| ) | ||
| reader, _ = self.handle_events_narrow(events) | ||
| reader, con = self.handle_events_narrow(events) | ||
| self.assertEqual(reader.cxy, (2, 3)) | ||
| con.restore() | ||
ambv marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| class WindowsConsoleGetEventTests(TestCase): | ||
ambv marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| # Virtual-Key Codes: https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes | ||
| VK_BACK = 0x08 | ||
| VK_RETURN = 0x0D | ||
| VK_LEFT = 0x25 | ||
| VK_7 = 0x37 | ||
| VK_M = 0x4D | ||
| # Used for miscellaneous characters; it can vary by keyboard. | ||
| # For the US standard keyboard, the '" key. | ||
| # For the German keyboard, the Ä key. | ||
| VK_OEM_7 = 0xDE | ||
| # State of control keys: https://learn.microsoft.com/en-us/windows/console/key-event-record-str | ||
| RIGHT_ALT_PRESSED = 0x0001 | ||
ambv marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| RIGHT_CTRL_PRESSED = 0x0004 | ||
| LEFT_ALT_PRESSED = 0x0002 | ||
| LEFT_CTRL_PRESSED = 0x0008 | ||
| ENHANCED_KEY = 0x0100 | ||
| SHIFT_PRESSED = 0x0010 | ||
| def get_event(self, input_records, **kwargs) -> Console: | ||
| self.console = WindowsConsole(encoding='utf-8') | ||
| self.mock = MagicMock(side_effect=input_records) | ||
| self.console._read_input = self.mock | ||
| self.console._WindowsConsole__vt_support = kwargs.get("vt_support", | ||
| False) | ||
| event = self.console.get_event(block=False) | ||
| return event | ||
| def get_input_record(self, unicode_char, vcode=0, control=0): | ||
| return wc.INPUT_RECORD( | ||
| wc.KEY_EVENT, | ||
| wc.ConsoleEvent(KeyEvent= | ||
| wc.KeyEvent( | ||
| bKeyDown=True, | ||
| wRepeatCount=1, | ||
| wVirtualKeyCode=vcode, | ||
| wVirtualScanCode=0, # not used | ||
| uChar=wc.Char(unicode_char), | ||
| dwControlKeyState=control | ||
| ))) | ||
| def test_EmptyBuffer(self): | ||
| self.assertEqual(self.get_event([None]), None) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_WINDOW_BUFFER_SIZE_EVENT(self): | ||
ambv marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| ir = wc.INPUT_RECORD( | ||
| wc.WINDOW_BUFFER_SIZE_EVENT, | ||
| wc.ConsoleEvent(WindowsBufferSizeEvent= | ||
| wc.WindowsBufferSizeEvent( | ||
| wc._COORD(0, 0)))) | ||
| self.assertEqual(self.get_event([ir]), Event("resize", "")) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_KEY_EVENT_up_ignored(self): | ||
| ir = wc.INPUT_RECORD( | ||
| wc.KEY_EVENT, | ||
| wc.ConsoleEvent(KeyEvent= | ||
| wc.KeyEvent(bKeyDown=False))) | ||
| self.assertEqual(self.get_event([ir]), None) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_unhandled_events(self): | ||
| for event in (wc.FOCUS_EVENT, wc.MENU_EVENT, wc.MOUSE_EVENT): | ||
| ir = wc.INPUT_RECORD( | ||
| event, | ||
| # fake data, nothing is read except bKeyDown | ||
| wc.ConsoleEvent(KeyEvent= | ||
| wc.KeyEvent(bKeyDown=False))) | ||
| self.assertEqual(self.get_event([ir]), None) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_enter(self): | ||
| ir = self.get_input_record("\r", self.VK_RETURN) | ||
| self.assertEqual(self.get_event([ir]), Event("key", "\n")) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_backspace(self): | ||
| ir = self.get_input_record("\x08", self.VK_BACK) | ||
| self.assertEqual( | ||
| self.get_event([ir]), Event("key", "backspace")) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_m(self): | ||
| ir = self.get_input_record("m", self.VK_M) | ||
| self.assertEqual(self.get_event([ir]), Event("key", "m")) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_M(self): | ||
| ir = self.get_input_record("M", self.VK_M, self.SHIFT_PRESSED) | ||
| self.assertEqual(self.get_event([ir]), Event("key", "M")) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_left(self): | ||
| # VK_LEFT is sent as ENHANCED_KEY | ||
| ir = self.get_input_record("\x00", self.VK_LEFT, self.ENHANCED_KEY) | ||
| self.assertEqual(self.get_event([ir]), Event("key", "left")) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_left_RIGHT_CTRL_PRESSED(self): | ||
| ir = self.get_input_record( | ||
| "\x00", self.VK_LEFT, self.RIGHT_CTRL_PRESSED | self.ENHANCED_KEY) | ||
| self.assertEqual( | ||
| self.get_event([ir]), Event("key", "ctrl left")) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_left_LEFT_CTRL_PRESSED(self): | ||
| ir = self.get_input_record( | ||
| "\x00", self.VK_LEFT, self.LEFT_CTRL_PRESSED | self.ENHANCED_KEY) | ||
| self.assertEqual( | ||
| self.get_event([ir]), Event("key", "ctrl left")) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_left_RIGHT_ALT_PRESSED(self): | ||
| ir = self.get_input_record( | ||
| "\x00", self.VK_LEFT, self.RIGHT_ALT_PRESSED | self.ENHANCED_KEY) | ||
| self.assertEqual(self.get_event([ir]), Event(evt="key", data="\033")) | ||
| self.assertEqual( | ||
| self.console.get_event(), Event("key", "left")) | ||
| # self.mock is not called again, since the second time we read from the | ||
| # command queue | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_left_LEFT_ALT_PRESSED(self): | ||
| ir = self.get_input_record( | ||
| "\x00", self.VK_LEFT, self.LEFT_ALT_PRESSED | self.ENHANCED_KEY) | ||
| self.assertEqual(self.get_event([ir]), Event(evt="key", data="\033")) | ||
| self.assertEqual( | ||
| self.console.get_event(), Event("key", "left")) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_m_LEFT_ALT_PRESSED_and_LEFT_CTRL_PRESSED(self): | ||
| # For the shift keys, Windows does not send anything when | ||
| # ALT and CTRL are both pressed, so let's test with VK_M. | ||
| # get_event() receives this input, but does not | ||
| # generate an event. | ||
| # This is for e.g. an English keyboard layout, for a | ||
| # German layout this returns `µ`, see test_AltGr_m. | ||
| ir = self.get_input_record( | ||
| "\x00", self.VK_M, self.LEFT_ALT_PRESSED | self.LEFT_CTRL_PRESSED) | ||
| self.assertEqual(self.get_event([ir]), None) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_m_LEFT_ALT_PRESSED(self): | ||
| ir = self.get_input_record( | ||
| "m", vcode=self.VK_M, control=self.LEFT_ALT_PRESSED) | ||
| self.assertEqual(self.get_event([ir]), Event(evt="key", data="\033")) | ||
| self.assertEqual(self.console.get_event(), Event("key", "m")) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_m_RIGHT_ALT_PRESSED(self): | ||
| ir = self.get_input_record( | ||
| "m", vcode=self.VK_M, control=self.RIGHT_ALT_PRESSED) | ||
| self.assertEqual(self.get_event([ir]), Event(evt="key", data="\033")) | ||
| self.assertEqual(self.console.get_event(), Event("key", "m")) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_AltGr_7(self): | ||
| # E.g. on a German keyboard layout, '{' is entered via | ||
| # AltGr + 7, where AltGr is the right Alt key on the keyboard. | ||
| # In this case, Windows automatically sets | ||
| # RIGHT_ALT_PRESSED = 0x0001 + LEFT_CTRL_PRESSED = 0x0008 | ||
| # This can also be entered like | ||
| # LeftAlt + LeftCtrl + 7 or | ||
| # LeftAlt + RightCtrl + 7 | ||
| # See https://learn.microsoft.com/en-us/windows/console/key-event-record-str | ||
| # https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-vkkeyscanw | ||
| ir = self.get_input_record( | ||
| "{", vcode=self.VK_7, | ||
| control=self.RIGHT_ALT_PRESSED | self.LEFT_CTRL_PRESSED) | ||
| self.assertEqual(self.get_event([ir]), Event("key", "{")) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_AltGr_m(self): | ||
| # E.g. on a German keyboard layout, this yields 'µ' | ||
| # Let's use LEFT_ALT_PRESSED and RIGHT_CTRL_PRESSED this | ||
| # time, to cover that, too. See above in test_AltGr_7. | ||
| ir = self.get_input_record( | ||
| "µ", vcode=self.VK_M, control=self.LEFT_ALT_PRESSED | self.RIGHT_CTRL_PRESSED) | ||
| self.assertEqual(self.get_event([ir]), Event("key", "µ")) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_umlaut_a_german(self): | ||
| ir = self.get_input_record("ä", self.VK_OEM_7) | ||
| self.assertEqual(self.get_event([ir]), Event("key", "ä")) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| # virtual terminal tests | ||
| # Note: wVirtualKeyCode, wVirtualScanCode and dwControlKeyState | ||
| # are always zero in this case. | ||
| # "\r" and backspace are handled specially, everything else | ||
| # is handled in "elif self.__vt_support:" in WindowsConsole.get_event(). | ||
| # Hence, only one regular key ("m") and a terminal sequence | ||
| # are sufficient to test here, the real tests happen in test_eventqueue | ||
| # and test_keymap. | ||
| def test_enter_vt(self): | ||
| ir = self.get_input_record("\r") | ||
| self.assertEqual(self.get_event([ir], vt_support=True), | ||
| Event("key", "\n")) | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_backspace_vt(self): | ||
| ir = self.get_input_record("\x7f") | ||
| self.assertEqual(self.get_event([ir], vt_support=True), | ||
| Event("key", "backspace", b"\x7f")) | ||
ambv marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| self.assertEqual(self.mock.call_count, 1) | ||
| def test_up_vt(self): | ||
| irs = [self.get_input_record(x) for x in "\x1b[A"] | ||
| self.assertEqual(self.get_event(irs, vt_support=True), | ||
| Event(evt='key', data='up', raw=bytearray(b'\x1b[A'))) | ||
| self.assertEqual(self.mock.call_count, 3) | ||
| if __name__ == "__main__": | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Fix ``PyREPL`` on Windows: characters entered via AltGr are swallowed. | ||
| Patch by Chris Eibl. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.