|
34 | 34 | import unicodedata |
35 | 35 | import warnings |
36 | 36 | from collections import deque, defaultdict |
| 37 | +from enum import IntFlag |
37 | 38 | from functools import lru_cache |
38 | 39 | from typing import Any, Callable, Dict, Generator, List, NamedTuple, Optional, Set, Sequence, TextIO, TypeVar |
39 | 40 |
|
|
52 | 53 | KT = TypeVar("KT") |
53 | 54 | VT = TypeVar("VT") |
54 | 55 |
|
| 56 | + |
| 57 | +class KeyboardFlags(IntFlag): |
| 58 | + DEFAULT = 0 |
| 59 | + """ |
| 60 | + All progressive enhancements disabled |
| 61 | + """ |
| 62 | + |
| 63 | + DISAMBIGUATE_ESCAPE_CODES = 1 |
| 64 | + """ |
| 65 | + This type of progressive enhancement (0b1) fixes the problem of some legacy |
| 66 | + key press encodings overlapping with other control codes. For instance, |
| 67 | + pressing the Esc key generates the byte 0x1b which also is used to indicate |
| 68 | + the start of an escape code. Similarly pressing the key alt+[ will generate |
| 69 | + the bytes used for CSI control codes. |
| 70 | +
|
| 71 | + Turning on this flag will cause the terminal to report the Esc, alt+key, |
| 72 | + ctrl+key, ctrl+alt+key, shift+alt+key keys using CSI u sequences instead of |
| 73 | + legacy ones. Here key is any ASCII key as described in Legacy text keys. |
| 74 | + Additionally, all non text keypad keys will be reported as separate keys |
| 75 | + with CSI u encoding, using dedicated numbers from the table below. |
| 76 | +
|
| 77 | + With this flag turned on, all key events that do not generate text are |
| 78 | + represented in one of the following two forms: |
| 79 | +
|
| 80 | + .. code: |
| 81 | +
|
| 82 | + CSI number; modifier u |
| 83 | + CSI 1; modifier [~ABCDEFHPQS] |
| 84 | +
|
| 85 | + This makes it very easy to parse key events in an application. In |
| 86 | + particular, ctrl+c will no longer generate the SIGINT signal, but instead |
| 87 | + be delivered as a CSI u escape code. This has the nice side effect of |
| 88 | + making it much easier to integrate into the application event loop. The |
| 89 | + only exceptions are the Enter, Tab and Backspace keys which still generate |
| 90 | + the same bytes as in legacy mode this is to allow the user to type and |
| 91 | + execute commands in the shell such as reset after a program that sets this |
| 92 | + mode crashes without clearing it. Note that the Lock modifiers are not |
| 93 | + reported for text producing keys, to keep them useable in legacy programs. |
| 94 | + To get lock modifiers for all keys use the Report all keys as escape codes |
| 95 | + enhancement. |
| 96 | + """ |
| 97 | + |
| 98 | + REPORT_EVENT_TYPES = 2 |
| 99 | + """ |
| 100 | + This progressive enhancement (0b10) causes the terminal to report key |
| 101 | + repeat and key release events. Normally only key press events are reported |
| 102 | + and key repeat events are treated as key press events. See Event types for |
| 103 | + details on how these are reported. |
| 104 | + """ |
| 105 | + |
| 106 | + REPORT_ALTERNATE_KEYS = 4 |
| 107 | + """ |
| 108 | + This progressive enhancement (0b100) causes the terminal to report |
| 109 | + alternate key values in addition to the main value, to aid in shortcut |
| 110 | + matching. See Key codes for details on how these are reported. Note that |
| 111 | + this flag is a pure enhancement to the form of the escape code used to |
| 112 | + represent key events, only key events represented as escape codes due to |
| 113 | + the other enhancements in effect will be affected by this enhancement. In |
| 114 | + other words, only if a key event was already going to be represented as an |
| 115 | + escape code due to one of the other enhancements will this enhancement |
| 116 | + affect it. |
| 117 | + """ |
| 118 | + |
| 119 | + RPORT_ALL_KEYS_AS_ESCAPE_CODES = 8 |
| 120 | + """ |
| 121 | + Key events that generate text, such as plain key presses without modifiers, |
| 122 | + result in just the text being sent, in the legacy protocol. There is no way |
| 123 | + to be notified of key repeat/release events. These types of events are |
| 124 | + needed for some applications, such as games (think of movement using the |
| 125 | + WASD keys). |
| 126 | +
|
| 127 | + This progressive enhancement (0b1000) turns on key reporting even for key |
| 128 | + events that generate text. When it is enabled, text will not be sent, |
| 129 | + instead only key events are sent. If the text is needed as well, combine |
| 130 | + with the Report associated text enhancement below. |
| 131 | +
|
| 132 | + Additionally, with this mode, events for pressing modifier keys are |
| 133 | + reported. Note that all keys are reported as escape codes, including Enter, |
| 134 | + Tab, Backspace etc. Note that this enhancement implies all keys are |
| 135 | + automatically disambiguated as well, since they are represented in their |
| 136 | + canonical escape code form. |
| 137 | + """ |
| 138 | + |
| 139 | + REPORT_ASSOCIATED_TEXT = 16 |
| 140 | + """ |
| 141 | + This progressive enhancement (0b10000) additionally causes key events that |
| 142 | + generate text to be reported as CSI u escape codes with the text embedded |
| 143 | + in the escape code. See Text as code points above for details on the |
| 144 | + mechanism. Note that this flag is an enhancement to Report all keys as |
| 145 | + escape codes and is undefined if used without it. |
| 146 | + """ |
| 147 | + |
| 148 | + |
55 | 149 | class Margins(NamedTuple): |
56 | 150 | """A container for screen's scroll margins.""" |
57 | 151 | top: int |
58 | 152 | bottom: int |
59 | 153 |
|
| 154 | + |
60 | 155 | class Savepoint(NamedTuple): |
61 | 156 | """A container for savepoint, created on :data:`~pyte.escape.DECSC`.""" |
62 | 157 | cursor: Cursor |
@@ -222,6 +317,7 @@ def __init__(self, columns: int, lines: int) -> None: |
222 | 317 | self.reset() |
223 | 318 | self.mode = _DEFAULT_MODE.copy() |
224 | 319 | self.margins: Optional[Margins] = None |
| 320 | + self._keyboard_flags: list[KeyboardFlags] = [KeyboardFlags.DEFAULT] |
225 | 321 |
|
226 | 322 | def __repr__(self) -> str: |
227 | 323 | return ("{0}({1}, {2})".format(self.__class__.__name__, |
@@ -439,6 +535,58 @@ def reset_mode(self, *modes: int, **kwargs: Any) -> None: |
439 | 535 | if mo.DECTCEM in mode_list: |
440 | 536 | self.cursor.hidden = True |
441 | 537 |
|
| 538 | + @property |
| 539 | + def keyboard_flags(self) -> KeyboardFlags: |
| 540 | + """Keyboard flags of current stack level. |
| 541 | +
|
| 542 | + Keyboard flags are to be used by terminal implementations to decide |
| 543 | + how to encode keyboard events sent to shell applications. |
| 544 | + """ |
| 545 | + return self._keyboard_flags[-1] |
| 546 | + |
| 547 | + def set_keyboard_flags(self, *args, private: bool = False, operator: str = "") -> None: |
| 548 | + """Handle progressive enhancement events. |
| 549 | +
|
| 550 | + Assign keyboard flags for shells supporting "progressive enhancements". |
| 551 | + see: https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement |
| 552 | + """ |
| 553 | + if private: |
| 554 | + # CSI ? u |
| 555 | + # progressive enhancment state query |
| 556 | + # report flags of current stack level |
| 557 | + self.write_process_input(str(self.keyboard_flags)) |
| 558 | + elif operator == "=": |
| 559 | + # assign/set/reset flags |
| 560 | + # CSI = u |
| 561 | + # CSI = mode u |
| 562 | + # CSI = flags ; mode u |
| 563 | + flags = KeyboardFlags.DEFAULT if len(args) == 0 else args[0] |
| 564 | + mode: int = 1 if len(args) < 2 else args[1] |
| 565 | + if mode == 1: |
| 566 | + # set all set and reset all unset bits |
| 567 | + self._keyboard_flags[-1] = flags |
| 568 | + elif mode == 2: |
| 569 | + # set all set and retain all unset bits |
| 570 | + self._keyboard_flags[-1] = self._keyboard_flags[-1] | flags |
| 571 | + elif mode == 3: |
| 572 | + # reset all set and retain all unset bits |
| 573 | + self._keyboard_flags[-1] = self._keyboard_flags[-1] & ~flags |
| 574 | + elif operator == ">": |
| 575 | + # push flags onto stack |
| 576 | + # CSI > u |
| 577 | + # CSI > flags u |
| 578 | + flags = KeyboardFlags.DEFAULT if len(args) == 0 else args[0] |
| 579 | + if len(self._keyboard_flags) < 99: |
| 580 | + self._keyboard_flags.append(flags) |
| 581 | + elif operator == "<": |
| 582 | + # pop flags from stack |
| 583 | + # CSI < u |
| 584 | + # CSI < count u |
| 585 | + count = 1 if len(args) == 0 else args[0] |
| 586 | + self._keyboard_flags = self._keyboard_flags[:-count] |
| 587 | + if len(self._keyboard_flags) == 0: |
| 588 | + self._keyboard_flags = [KeyboardFlags.DEFAULT] |
| 589 | + |
442 | 590 | def define_charset(self, code: str, mode: str) -> None: |
443 | 591 | """Define ``G0`` or ``G1`` charset. |
444 | 592 |
|
|
0 commit comments