|
1 | 1 | """ |
2 | | -Provide a blessed Terminal subclass for remote (SSH/telnet) streams. |
| 2 | +Provide common resources for both Telnet and SSH terminals. |
3 | 3 | """ |
4 | 4 |
|
5 | 5 | from __future__ import annotations |
6 | 6 |
|
7 | 7 | import codecs |
| 8 | +from concurrent.futures import ThreadPoolExecutor, CancelledError |
| 9 | +from enum import Enum |
8 | 10 | import hashlib |
9 | 11 | import contextlib |
10 | | -from typing import IO, Generator |
| 12 | +from typing import IO, Callable, Generator, TypeAlias, TYPE_CHECKING |
11 | 13 |
|
12 | 14 | from blessed import Terminal as BlessedTerminal |
13 | 15 | from blessed.terminal import WINSZ |
14 | 16 |
|
| 17 | +if TYPE_CHECKING: |
| 18 | + from .main import AppConfig |
| 19 | + |
15 | 20 |
|
16 | 21 | class RemoteTerminal(BlessedTerminal): |
17 | 22 | """A blessed Terminal subclass for remote streams (SSH, telnet). |
@@ -87,6 +92,55 @@ def update_size(self, rows: int, columns: int) -> None: |
87 | 92 | self._columns = columns |
88 | 93 |
|
89 | 94 |
|
| 95 | +class KeyboardSupport(Enum): |
| 96 | + BASIC = "basic" |
| 97 | + KEYBOARD_PROTOCOL = "keyboard_protocol" |
| 98 | + X11 = "x11" |
| 99 | + |
| 100 | + |
| 101 | +class KeyboardSupportDetection: |
| 102 | + def __init__( |
| 103 | + self, |
| 104 | + terminal: RemoteTerminal, |
| 105 | + display: str | None = None, |
| 106 | + executor: ThreadPoolExecutor | None = None, |
| 107 | + ) -> None: |
| 108 | + self.terminal = terminal |
| 109 | + self.display = display |
| 110 | + self.executor = executor |
| 111 | + self._cache: KeyboardSupport | None = None |
| 112 | + |
| 113 | + def get(self, timeout: float = 3.0) -> KeyboardSupport: |
| 114 | + if self._cache is not None: |
| 115 | + return self._cache |
| 116 | + self._cache = self._detect(timeout) |
| 117 | + return self._cache |
| 118 | + |
| 119 | + def _detect(self, timeout: float = 3.0) -> KeyboardSupport: |
| 120 | + from .keyboard_input import is_kitty_keyboard_protocol_supported |
| 121 | + |
| 122 | + if is_kitty_keyboard_protocol_supported(self.terminal, timeout=timeout): |
| 123 | + return KeyboardSupport.KEYBOARD_PROTOCOL |
| 124 | + |
| 125 | + elif self.display and self.executor: |
| 126 | + from .x11_keyboard_input import is_x11_display_functional |
| 127 | + |
| 128 | + try: |
| 129 | + if self.executor.submit(is_x11_display_functional, self.display).result( |
| 130 | + timeout=timeout |
| 131 | + ): |
| 132 | + return KeyboardSupport.X11 |
| 133 | + except CancelledError: |
| 134 | + pass |
| 135 | + |
| 136 | + return KeyboardSupport.BASIC |
| 137 | + |
| 138 | + |
| 139 | +FrontendCallback: TypeAlias = Callable[ |
| 140 | + [RemoteTerminal, "AppConfig", KeyboardSupportDetection], "AppConfig" |
| 141 | +] |
| 142 | + |
| 143 | + |
90 | 144 | def user_directory_name(username: str | None) -> str: |
91 | 145 | """Hash the username into a safe directory name. |
92 | 146 |
|
|
0 commit comments