mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-09-22 13:01:05 +03:00
eterm: harden threaded presentation and terminal protocol handling
- preserve CSI subparameter separators for modern SGR color sequences - fix synchronized updates and periodic presentation under sustained output - support OSC palette queries, resets, and color scheme notifications - implement alternate scroll, cursor style, and terminal version queries - preserve the configured cursor shape for default blinking cursor requests - recognize Kitty keyboard negotiation without falsely advertising support - recognize OSC 8 hyperlink markers without emitting parser errors - make release parser diagnostics opt-in through ETERM_LOG_ERRORS - update the Kitty keyboard protocol implementation plan for worker ownership - add regression coverage for rendering, colors, CSI modes, cursors, and OSC 8
This commit is contained in:
@@ -7,7 +7,13 @@
|
||||
- **Primary module:** `src/modules/eterm`
|
||||
- **Primary goal:** Implement Kitty keyboard protocol progressive enhancement in eTerm so modern terminal applications can distinguish key combinations that legacy terminal encoding collapses, especially `Ctrl+Enter`, `Shift+Enter`, modified Escape, modified Tab, modified Backspace, and modified printable keys.
|
||||
- **Initial motivating application:** OpenAI Codex CLI, which uses Crossterm keyboard enhancement flags.
|
||||
- **Expected result for the motivating case:** When Codex enables the Kitty keyboard protocol and the user presses `Ctrl+Enter`, eTerm must send `CSI 13;5u` (`ESC [ 1 3 ; 5 u`) instead of the same carriage-return byte sent for plain Enter.
|
||||
- **Expected result for the motivating case:** When an application enables
|
||||
`REPORT_ALL_KEYS_AS_ESCAPE_CODES` and the user presses `Ctrl+Enter`, eTerm must send
|
||||
`CSI 13;5u` (`ESC [ 1 3 ; 5 u`) instead of the same carriage-return byte sent for plain Enter.
|
||||
`DISAMBIGUATE_ESCAPE_CODES` alone deliberately preserves legacy Enter, Tab, and Backspace
|
||||
encodings. If Codex continues requesting only flags `7`, distinguishable `Ctrl+Enter` cannot be
|
||||
promised without a documented protocol deviation or an application-side change to request bit
|
||||
`8`.
|
||||
- **Compatibility requirement:** When no application has enabled the protocol, eTerm must preserve its existing legacy key behavior byte-for-byte.
|
||||
|
||||
---
|
||||
@@ -26,7 +32,7 @@ The proper fix is not an unconditional special case for Codex. eTerm must implem
|
||||
|
||||
1. Parse requests from the child application to query, push, set, or pop keyboard enhancement flags.
|
||||
2. Store enhancement state per terminal screen, with stack behavior.
|
||||
3. Reply to support queries.
|
||||
3. Reply to current-state queries only once enhanced key encoding is operational.
|
||||
4. Encode keyboard events according to the active flags.
|
||||
5. Continue using existing legacy mappings when the protocol is inactive.
|
||||
6. Support press, repeat, and release events when requested.
|
||||
@@ -38,8 +44,9 @@ The implementation should be split into four logical pieces:
|
||||
|
||||
- **Protocol/state model** in `TerminalEmulator`
|
||||
- **CSI command parsing and responses** in `TerminalEmulator::csihandle()`
|
||||
- **Key event encoding** in a focused encoder class/helper used by `TerminalDisplay`
|
||||
- **Input event plumbing** in `UITerminal` / `TerminalDisplay`, including key-up and repeat support
|
||||
- **Key event encoding** in a focused encoder class/helper invoked on the terminal worker
|
||||
- **Input event plumbing** from `UITerminal` / `TerminalDisplay` into typed `TerminalSession`
|
||||
commands, including key-up and repeat support
|
||||
|
||||
Do not mix the new protocol rules into the existing large `keys[]` table. Keep the legacy table intact as a fallback and add a separate enhanced encoder that runs before it when active.
|
||||
|
||||
@@ -178,30 +185,42 @@ Codex has used a combination including disambiguation, event types, and alternat
|
||||
|
||||
## 4.1 Event flow
|
||||
|
||||
The current flow is approximately:
|
||||
eTerm now has a dedicated `TerminalSession` worker. The worker exclusively owns the emulator,
|
||||
PTY, process, parser, terminal modes, history, and cursor. UI code may enqueue commands, drain
|
||||
events, and retain immutable snapshots, but it must never read mutable emulator state.
|
||||
|
||||
The current keyboard path still pre-encodes legacy bytes on the UI thread:
|
||||
|
||||
```text
|
||||
EEPP Window/Input backend
|
||||
-> UI event dispatcher
|
||||
-> UITerminal::onKeyDown(KeyEvent)
|
||||
-> TerminalDisplay::onKeyDown(keyCode, char, mod, scancode)
|
||||
-> terminal shortcuts
|
||||
-> Ctrl-letter handling
|
||||
-> legacy TerminalKeyMap
|
||||
-> PTY ttywrite()
|
||||
-> UITerminal / TerminalDisplay
|
||||
-> terminal-local shortcuts
|
||||
-> Ctrl-letter and legacy TerminalKeyMap encoding
|
||||
-> TerminalSession::write(encoded bytes)
|
||||
-> ordered worker command
|
||||
-> PTY ttywrite()
|
||||
```
|
||||
|
||||
Text input follows a separate path:
|
||||
The Kitty implementation must change the child-input portion to:
|
||||
|
||||
```text
|
||||
EEPP text input event
|
||||
-> UITerminal::onTextInput(TextInputEvent)
|
||||
-> TerminalDisplay::onTextInput(codepoint)
|
||||
-> UTF-8 conversion
|
||||
-> PTY ttywrite()
|
||||
EEPP Window/Input backend
|
||||
-> UI event dispatcher
|
||||
-> UITerminal / TerminalDisplay
|
||||
-> consume terminal-local shortcuts
|
||||
-> enqueue semantic KeyDown / KeyUp / TextInput command
|
||||
-> TerminalSession worker
|
||||
-> read authoritative Kitty state and terminal modes
|
||||
-> correlate key and committed-text events
|
||||
-> enhanced or legacy encoding
|
||||
-> PTY ttywrite()
|
||||
```
|
||||
|
||||
This split is important. Printable characters are normally emitted by `onTextInput()`, while special/control keys are emitted by `onKeyDown()`. Kitty’s `REPORT_ALL_KEYS_AS_ESCAPE_CODES` changes this assumption: printable keys may need to be emitted from the key-event path as CSI-u events, and the later text-input event must then be suppressed to prevent duplicate characters.
|
||||
Printable characters currently arrive through a separate text-input event. Kitty's
|
||||
`REPORT_ALL_KEYS_AS_ESCAPE_CODES` requires key/text correlation to avoid duplicate input. That
|
||||
correlation belongs on the worker because the ordered command queue preserves UI event order and
|
||||
the worker owns the negotiated state.
|
||||
|
||||
## 4.2 Existing legacy keyboard map
|
||||
|
||||
@@ -236,16 +255,18 @@ This is why modified Enter collapses to carriage return.
|
||||
|
||||
## 4.3 Existing parser support placeholder
|
||||
|
||||
`TerminalEmulator::csihandle()` already has a branch similar to:
|
||||
The CSI parser has now been characterized and updated. It stores `<`, `=`, `>`, or `?` in
|
||||
`CSIEscape::priv`, preserves `;`/`:` separators in `CSIEscape::sep`, and places the final byte in
|
||||
`mode[0]`. Therefore Kitty controls are represented as a prefixed final `u`, for example:
|
||||
|
||||
```cpp
|
||||
case '=': /* Progressive enhancement sequences */
|
||||
/* Keyboard protocol ESC[=Nu */
|
||||
/* Do nothing for the moment */
|
||||
break;
|
||||
```text
|
||||
ESC [ > 7 u -> priv='>', arg[0]=7, mode[0]='u'
|
||||
ESC [ ? u -> priv='?', arg[0]=0, mode[0]='u'
|
||||
```
|
||||
|
||||
This is a strong signal that progressive enhancement belongs in `TerminalEmulator`, not in the UI widget.
|
||||
`TerminalEmulator::csihandle()` currently recognizes all four prefixed `u` variants and silently
|
||||
ignores them. It intentionally does not answer `CSI ? u`, because a response would claim support
|
||||
before enhanced input encoding exists. Bare `CSI u` continues to mean DECRC cursor restore.
|
||||
|
||||
The CSI parser stores:
|
||||
|
||||
@@ -260,7 +281,7 @@ struct CSIEscape {
|
||||
};
|
||||
```
|
||||
|
||||
Before implementation, verify exactly how these sequences are represented after `csiparse()`:
|
||||
The existing parser regression test covers these exact byte strings:
|
||||
|
||||
```text
|
||||
ESC [ ? u
|
||||
@@ -273,7 +294,10 @@ ESC [ = 7 ; 2 u
|
||||
ESC [ = 7 ; 3 u
|
||||
```
|
||||
|
||||
Do not assume that `?`, `>`, `<`, and `=` always land in the same field. The current `csihandle()` switch structure suggests some private/intermediate bytes may be placed into `mode[0]` or `priv`. Add parser tests first and adjust the handler according to observed representation.
|
||||
Extend that test into state/response assertions when negotiation is implemented. One remaining
|
||||
detail is that the parser currently represents an omitted numeric parameter as zero. If exact
|
||||
semantics require distinguishing omitted `CSI < u` from explicit `CSI < 0 u`, add argument-presence
|
||||
metadata rather than inferring presence from `narg`.
|
||||
|
||||
## 4.4 Screen swapping
|
||||
|
||||
@@ -304,10 +328,10 @@ src/modules/eterm/include/eterm/terminal/kittykeyboardprotocol.hpp
|
||||
src/modules/eterm/src/eterm/terminal/kittykeyboardprotocol.cpp
|
||||
```
|
||||
|
||||
Possible test file:
|
||||
Use the repository's existing unit-test file:
|
||||
|
||||
```text
|
||||
src/modules/eterm/tests/kittykeyboardprotocol_test.cpp
|
||||
src/tests/unit_tests/eterm_tests.cpp
|
||||
```
|
||||
|
||||
Adapt to the repository’s actual test layout and build system.
|
||||
@@ -420,7 +444,7 @@ Names may be adjusted to project style, but maintain clear separation between:
|
||||
- output bytes,
|
||||
- duplicate-text suppression.
|
||||
|
||||
## 5.2 Keep protocol state owned by `TerminalEmulator`
|
||||
## 5.2 Keep protocol state and encoding on the worker
|
||||
|
||||
`TerminalEmulator` receives application output and therefore owns negotiation state.
|
||||
|
||||
@@ -431,14 +455,7 @@ KittyKeyboardState mPrimaryKeyboardState;
|
||||
KittyKeyboardState mAlternateKeyboardState;
|
||||
```
|
||||
|
||||
Add public read-only accessors and event-state operations:
|
||||
|
||||
```cpp
|
||||
std::uint32_t getKeyboardEnhancementFlags() const;
|
||||
bool hasKeyboardEnhancementFlag( KittyKeyboardFlag flag ) const;
|
||||
```
|
||||
|
||||
Potentially add:
|
||||
Potentially add private worker-only helpers:
|
||||
|
||||
```cpp
|
||||
const KittyKeyboardState& getKeyboardState() const;
|
||||
@@ -447,7 +464,11 @@ KittyKeyboardState& getKeyboardState();
|
||||
|
||||
Keep mutating access private if possible.
|
||||
|
||||
`TerminalDisplay` should only query active flags. It must not parse application control sequences or mutate protocol stacks directly.
|
||||
Do not add a UI-facing `getKeyboardEnhancementFlags()` accessor. `TerminalDisplay` must not read
|
||||
the emulator or use snapshot flags to decide how to encode input: snapshots can lag negotiation.
|
||||
Instead add typed key and text commands to `TerminalSession`; their worker-side handlers query the
|
||||
active state and invoke the encoder. Active flags may be copied into `TerminalSnapshot` for
|
||||
diagnostics only.
|
||||
|
||||
## 5.3 Consider moving `sanitizeMod()`
|
||||
|
||||
@@ -485,7 +506,10 @@ DisambiguateEscapeCodes
|
||||
|
||||
Add `ReportAlternateKeys` only if EEPP can reliably supply shifted/base-layout key values. Add `ReportAssociatedText` only when the implementation can correlate committed text with the originating key event without duplication or loss.
|
||||
|
||||
However, for Codex compatibility, determine whether Crossterm requires the terminal to echo all requested bits or merely accepts the subset returned by the query. The preferred behavior is standards-compliant subset support, not falsely claiming unsupported features.
|
||||
When an application sets or pushes flags, mask the request to features eTerm can honor. A later
|
||||
current-state query reports the effective active subset, allowing applications to observe which
|
||||
requested bits took effect. Do not treat the initial query as capability advertisement and do not
|
||||
retain unsupported bits as active.
|
||||
|
||||
A safe first complete target is to implement all five flags to the degree required by the spec, with documented backend limitations.
|
||||
|
||||
@@ -497,7 +521,8 @@ When the child sends:
|
||||
CSI ? u
|
||||
```
|
||||
|
||||
eTerm must respond through the PTY input channel with:
|
||||
eTerm must respond through the PTY input channel with the current effective flags for the active
|
||||
screen:
|
||||
|
||||
```text
|
||||
CSI ? <flags> u
|
||||
@@ -509,24 +534,29 @@ For example:
|
||||
ESC [ ? 31 u
|
||||
```
|
||||
|
||||
if all five flags are supported.
|
||||
if all five flags are currently active. A newly initialized terminal responds with `CSI ? 0 u`
|
||||
even if it implements every enhancement. This is a state query, not a supported-capability mask.
|
||||
|
||||
Use `ttywrite()` or the appropriate low-level response method with echo disabled. Follow existing device-status response patterns.
|
||||
|
||||
Suggested helper:
|
||||
Suggested worker-only helper:
|
||||
|
||||
```cpp
|
||||
void TerminalEmulator::replyKeyboardEnhancementFlags() {
|
||||
void TerminalEmulator::replyKeyboardEnhancementState() {
|
||||
char buf[32];
|
||||
const int len = std::snprintf(
|
||||
buf, sizeof( buf ), "\033[?%uu",
|
||||
KITTY_KEYBOARD_SUPPORTED_FLAGS
|
||||
activeKeyboardState().flags
|
||||
);
|
||||
ttywrite( buf, static_cast<std::size_t>( len ), 0 );
|
||||
}
|
||||
```
|
||||
|
||||
Important: verify from the official spec whether the query response reports **supported flags** or **currently active flags**. Implement exactly the current spec. Do not infer from naming. Add a test matching Kitty and Crossterm behavior.
|
||||
Applications detect protocol support by sending the state query followed by primary device
|
||||
attributes. No Kitty response before DA means unsupported. To discover whether requested bits took
|
||||
effect, an application sets/pushes them and then queries the resulting current state. Keep a
|
||||
supported mask internally for safe flag application, but never return that mask in place of the
|
||||
active state.
|
||||
|
||||
## 6.3 Push semantics
|
||||
|
||||
@@ -602,15 +632,11 @@ The protocol includes:
|
||||
CSI = flags ; mode u
|
||||
```
|
||||
|
||||
The `mode` controls how `flags` affects current state. Implement all standardized modes exactly.
|
||||
The optional `mode` defaults to `1` and has these standardized meanings:
|
||||
|
||||
Expected conceptual operations are generally:
|
||||
|
||||
- replace/set absolute flags,
|
||||
- set/add bits,
|
||||
- clear/remove bits.
|
||||
|
||||
Do not hardcode these assumptions without checking the current official table.
|
||||
- `1`: replace the current state; set specified bits and reset all unspecified bits,
|
||||
- `2`: set the specified bits and leave unspecified bits unchanged,
|
||||
- `3`: reset the specified bits and leave unspecified bits unchanged.
|
||||
|
||||
Implement through one function:
|
||||
|
||||
@@ -623,13 +649,13 @@ void KittyKeyboardState::set(
|
||||
requestedFlags & KITTY_KEYBOARD_SUPPORTED_FLAGS;
|
||||
|
||||
switch ( mode ) {
|
||||
case /* set */:
|
||||
case 1:
|
||||
flags = supported;
|
||||
break;
|
||||
case /* OR */:
|
||||
case 2:
|
||||
flags |= supported;
|
||||
break;
|
||||
case /* clear */:
|
||||
case 3:
|
||||
flags &= ~supported;
|
||||
break;
|
||||
default:
|
||||
@@ -685,9 +711,12 @@ Do not leave enhancement flags active when a new shell is spawned in the same te
|
||||
|
||||
# 7. CSI parser implementation
|
||||
|
||||
## 7.1 Add parser characterization tests first
|
||||
## 7.1 Extend the existing parser characterization test
|
||||
|
||||
Before modifying `csihandle()`, create tests that feed these exact byte strings into `TerminalEmulator`:
|
||||
The existing `modern_csi_prefixes_do_not_claim_unsupported_keyboard_protocol` test already feeds
|
||||
the representative query/push/pop/set strings into `TerminalEmulator` and verifies that the
|
||||
unsupported implementation stays silent. Preserve it until negotiation and encoding land
|
||||
together, then extend it to assert state changes and responses:
|
||||
|
||||
```cpp
|
||||
"\033[?u"
|
||||
@@ -701,7 +730,7 @@ Before modifying `csihandle()`, create tests that feed these exact byte strings
|
||||
"\033[=1;3u"
|
||||
```
|
||||
|
||||
Expose or instrument parsed `CSIEscape` only in tests if needed. Capture:
|
||||
The established parser representation is:
|
||||
|
||||
- `priv`
|
||||
- `arg[]`
|
||||
@@ -710,7 +739,7 @@ Expose or instrument parsed `CSIEscape` only in tests if needed. Capture:
|
||||
- `mode[1]`
|
||||
- raw buffer
|
||||
|
||||
This prevents implementing against an incorrect mental model of the inherited `st` parser.
|
||||
Do not add test-only parser exposure unless a future parser change requires it.
|
||||
|
||||
## 7.2 Add a dedicated handler
|
||||
|
||||
@@ -798,7 +827,8 @@ constexpr unsigned int MAX_POP_COUNT = 64;
|
||||
|
||||
The control sequence is application output; the response must be terminal input.
|
||||
|
||||
Use the same response path as device status reports. Ensure:
|
||||
Handle and answer the query on the `TerminalSession` worker, using the same response path as device
|
||||
status reports. Ensure:
|
||||
|
||||
- no local echo,
|
||||
- no screen rendering,
|
||||
@@ -807,7 +837,9 @@ Use the same response path as device status reports. Ensure:
|
||||
|
||||
## 7.6 Debug tracing
|
||||
|
||||
Add optional protocol tracing controlled by a compile-time flag or existing logger level:
|
||||
Use the existing `ETERM_LOG_ERRORS` diagnostics switch for malformed protocol diagnostics. If
|
||||
verbose per-key tracing is later needed, give it a distinct trace-level switch so
|
||||
`ETERM_LOG_ERRORS=1` does not log every key:
|
||||
|
||||
```text
|
||||
eterm kitty-kbd: query -> response flags=31
|
||||
@@ -822,6 +854,12 @@ Do not log every key at normal log levels.
|
||||
|
||||
# 8. Key event model and EEPP plumbing
|
||||
|
||||
The UI thread may identify terminal-local shortcuts and normalize EEPP event data, but it must
|
||||
enqueue semantic events rather than encoded bytes. Add `KeyDownCommand`, `KeyUpCommand`, and
|
||||
`TextInputCommand` to `TerminalSession`. The worker performs event correlation, reads the active
|
||||
screen's Kitty state and terminal modes, chooses enhanced versus legacy encoding, and writes to
|
||||
the PTY.
|
||||
|
||||
## 8.1 Add key-up forwarding
|
||||
|
||||
Current `UITerminal::onKeyUp()` returns `1` without forwarding:
|
||||
@@ -832,7 +870,7 @@ Uint32 UITerminal::onKeyUp( const KeyEvent& ) {
|
||||
}
|
||||
```
|
||||
|
||||
To implement release reporting:
|
||||
To implement release reporting, forward a semantic release event into `TerminalSession`:
|
||||
|
||||
```cpp
|
||||
Uint32 UITerminal::onKeyUp( const KeyEvent& event ) {
|
||||
@@ -850,7 +888,7 @@ Uint32 UITerminal::onKeyUp( const KeyEvent& event ) {
|
||||
|
||||
Add `TerminalDisplay::onKeyUp()`.
|
||||
|
||||
Only emit a release sequence when:
|
||||
The worker only emits a release sequence when:
|
||||
|
||||
```cpp
|
||||
activeFlags & REPORT_EVENT_TYPES
|
||||
@@ -1078,11 +1116,11 @@ Do not invent code values. Copy them from the official key-code table and add a
|
||||
|
||||
## 10.3 Enter special case
|
||||
|
||||
With `DISAMBIGUATE_ESCAPE_CODES` enabled:
|
||||
With only `DISAMBIGUATE_ESCAPE_CODES` enabled, Enter remains a legacy exception regardless of
|
||||
modifiers. The same exception applies to Tab and Backspace, allowing a user to type `reset` if an
|
||||
application crashes without restoring modes.
|
||||
|
||||
- Plain Enter may retain legacy `\r` unless another active flag requires CSI-u.
|
||||
- Modified Enter must be encoded as CSI-u.
|
||||
- Ctrl+Enter must become:
|
||||
With `REPORT_ALL_KEYS_AS_ESCAPE_CODES` enabled, Enter is encoded as CSI-u. Ctrl+Enter becomes:
|
||||
|
||||
```text
|
||||
CSI 13 ; 5 u
|
||||
@@ -1108,7 +1146,9 @@ Ctrl+Shift+Enter:
|
||||
CSI 13 ; 6 u
|
||||
```
|
||||
|
||||
When `REPORT_ALL_KEYS_AS_ESCAPE_CODES` is active, plain Enter should also use the protocol form required by the spec.
|
||||
Plain Enter also uses the protocol form when report-all is active. Event-type fields are included
|
||||
only when requested. Tests and acceptance criteria must not expect `CSI 13;5u` from disambiguation
|
||||
alone.
|
||||
|
||||
## 10.4 Legacy cursor/function-key forms under enhancement
|
||||
|
||||
@@ -1205,7 +1245,8 @@ This feature should be implemented only after input-event correlation is sound.
|
||||
|
||||
# 12. Progressive enhancement decision algorithm
|
||||
|
||||
Implement one deterministic function that decides enhanced versus legacy output.
|
||||
Implement one deterministic function that decides enhanced versus legacy output. It is invoked by
|
||||
the `TerminalSession` worker, never by rendering/UI code.
|
||||
|
||||
Pseudo-code:
|
||||
|
||||
@@ -1254,7 +1295,9 @@ KittyEncodedKey KittyKeyboardEncoder::encode(
|
||||
}
|
||||
```
|
||||
|
||||
The exact `mustEncode` rules must match the spec. The above is architecture, not a substitute for the official rules.
|
||||
The exact `mustEncode` rules must match the spec. In particular, Enter, Tab, and Backspace remain
|
||||
legacy under disambiguation alone. The above is architecture, not a substitute for the official
|
||||
rules.
|
||||
|
||||
---
|
||||
|
||||
@@ -1273,9 +1316,10 @@ TextInput("a")
|
||||
|
||||
If Kitty `REPORT_ALL_KEYS_AS_ESCAPE_CODES` causes `KeyDown(A)` to emit a CSI-u sequence and `TextInput("a")` still emits `a`, the child receives the key twice.
|
||||
|
||||
## 13.2 Preferred solution: correlated suppression queue
|
||||
## 13.2 Preferred solution: worker-side correlated suppression queue
|
||||
|
||||
Add a small queue of expected text-input codepoints after an enhanced keydown consumed the event:
|
||||
Add a small queue of expected text-input codepoints after an enhanced keydown consumed the event.
|
||||
Keep this queue beside the worker-side encoder, not in `TerminalDisplay`:
|
||||
|
||||
```cpp
|
||||
struct SuppressedTextInput {
|
||||
@@ -1308,7 +1352,7 @@ For printable keys under `REPORT_ALL_KEYS_AS_ESCAPE_CODES`:
|
||||
|
||||
This is more correct for keyboard layouts and IME but requires careful event ordering and latency handling.
|
||||
|
||||
Recommended architecture:
|
||||
Recommended worker-side architecture:
|
||||
|
||||
```cpp
|
||||
std::optional<PendingKittyTextKey> mPendingTextKey;
|
||||
@@ -1357,16 +1401,11 @@ Do not ship an implementation that duplicates printable characters.
|
||||
|
||||
# 14. Changes to `TerminalDisplay`
|
||||
|
||||
## 14.1 Add active flag accessor
|
||||
## 14.1 Do not expose active flags to the display
|
||||
|
||||
Use:
|
||||
|
||||
```cpp
|
||||
const auto flags =
|
||||
mTerminal->getKeyboardEnhancementFlags();
|
||||
```
|
||||
|
||||
Do not cache flags in `TerminalDisplay`; application output may change them at any moment.
|
||||
`TerminalDisplay` must not query `TerminalEmulator`, and a flag value copied through
|
||||
`TerminalSnapshot` is not authoritative enough for input encoding. The display handles local UI
|
||||
shortcuts and forwards the remaining semantic event to `TerminalSession`.
|
||||
|
||||
## 14.2 Refactor `onKeyDown()`
|
||||
|
||||
@@ -1388,7 +1427,7 @@ void TerminalDisplay::onKeyDown(
|
||||
if ( handleTerminalShortcut( keyCode, smod ) )
|
||||
return;
|
||||
|
||||
const KittyKeyEvent event{
|
||||
const TerminalKeyEvent event{
|
||||
keyCode,
|
||||
scancode,
|
||||
chr,
|
||||
@@ -1398,33 +1437,17 @@ void TerminalDisplay::onKeyDown(
|
||||
: KittyKeyEventType::Press
|
||||
};
|
||||
|
||||
const auto enhanced = KittyKeyboardEncoder::encode(
|
||||
event,
|
||||
mTerminal->getKeyboardEnhancementFlags()
|
||||
);
|
||||
|
||||
if ( enhanced.handled ) {
|
||||
mTerminal->ttywrite(
|
||||
enhanced.bytes.data(),
|
||||
enhanced.bytes.size(),
|
||||
1
|
||||
);
|
||||
|
||||
if ( enhanced.suppressTextInput )
|
||||
registerTextInputSuppression( event );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
handleLegacyKeyDown( keyCode, chr, mod, scancode );
|
||||
mSession->keyDown( std::move( event ) );
|
||||
}
|
||||
```
|
||||
|
||||
Extract current shortcut and legacy map logic into helpers to make tests possible:
|
||||
Extract current shortcut logic into a helper. Move the child-facing Ctrl conversion and legacy
|
||||
key map into a worker-side encoder/fallback so both legacy and enhanced output use authoritative
|
||||
terminal modes:
|
||||
|
||||
```cpp
|
||||
bool handleTerminalShortcut(...);
|
||||
void handleLegacyKeyDown(...);
|
||||
KittyEncodedKey encodeChildKeyEvent(...); // worker only
|
||||
```
|
||||
|
||||
## 14.3 Add `onKeyUp()`
|
||||
@@ -1436,33 +1459,20 @@ void TerminalDisplay::onKeyUp(
|
||||
const Uint32& mod,
|
||||
const Scancode& scancode
|
||||
) {
|
||||
const auto flags =
|
||||
mTerminal->getKeyboardEnhancementFlags();
|
||||
|
||||
if ( !( flags & REPORT_EVENT_TYPES ) )
|
||||
return;
|
||||
|
||||
KittyKeyEvent event{
|
||||
TerminalKeyEvent event{
|
||||
keyCode,
|
||||
scancode,
|
||||
chr,
|
||||
mod,
|
||||
KittyKeyEventType::Release
|
||||
};
|
||||
|
||||
const auto encoded =
|
||||
KittyKeyboardEncoder::encode( event, flags );
|
||||
|
||||
if ( encoded.handled )
|
||||
mTerminal->ttywrite(
|
||||
encoded.bytes.data(),
|
||||
encoded.bytes.size(),
|
||||
1
|
||||
);
|
||||
mSession->keyUp( std::move( event ) );
|
||||
}
|
||||
```
|
||||
|
||||
Terminal shortcuts consumed on keydown should generally not emit release events to the child. Track consumed physical keys until key-up if necessary:
|
||||
The worker emits a release only if the negotiated flags require it. Terminal shortcuts consumed
|
||||
on keydown should generally not emit release events to the child. Track consumed physical keys on
|
||||
the UI side until key-up if necessary:
|
||||
|
||||
```cpp
|
||||
std::unordered_set<Scancode> mLocallyConsumedKeys;
|
||||
@@ -1474,17 +1484,15 @@ Refactor:
|
||||
|
||||
```cpp
|
||||
void TerminalDisplay::onTextInput( const Uint32& chr ) {
|
||||
if ( !mTerminal )
|
||||
if ( !mSession )
|
||||
return;
|
||||
|
||||
if ( handlePendingKittyTextInput( chr ) )
|
||||
return;
|
||||
|
||||
// Existing UTF-8 legacy behavior.
|
||||
mSession->textInput( chr );
|
||||
}
|
||||
```
|
||||
|
||||
Ensure a multi-codepoint text-input event is handled correctly. Current API appears to pass one `Uint32`; inspect whether EEPP emits one event per codepoint.
|
||||
The worker correlates this command with pending key events and chooses enhanced versus legacy
|
||||
output. Ensure a multi-codepoint text-input event is handled correctly. Current API appears to
|
||||
pass one `Uint32`; inspect whether EEPP emits one event per codepoint.
|
||||
|
||||
---
|
||||
|
||||
@@ -1494,6 +1502,7 @@ Required changes:
|
||||
|
||||
- Forward key-up.
|
||||
- Forward repeat status if `KeyEvent` exposes it.
|
||||
- Forward semantic key/text data through `TerminalSession`; never read emulator state.
|
||||
- Preserve focus checks.
|
||||
- Preserve custom keybinding command interception.
|
||||
- Clear pending Kitty input state on focus loss if there is an existing focus callback.
|
||||
@@ -1525,8 +1534,8 @@ Update interfaces and all callers.
|
||||
|
||||
When active flags are zero:
|
||||
|
||||
- `TerminalDisplay::onKeyDown()` must execute the old code path.
|
||||
- `onTextInput()` must behave exactly as before.
|
||||
- semantic key/text commands must produce the same bytes as the old `TerminalDisplay` path,
|
||||
- worker-side legacy text input must behave exactly as before,
|
||||
- `onKeyUp()` must emit nothing.
|
||||
- Existing `Ctrl+A` through control-code conversion must remain unchanged.
|
||||
- Alt+Enter must retain `ESC CR`.
|
||||
@@ -1540,7 +1549,9 @@ Add regression tests comparing old expected output bytes.
|
||||
|
||||
# 17. Handling Ctrl-letter logic
|
||||
|
||||
Current code manually maps Ctrl plus certain scancodes to bytes `0x01` etc. This block must occur **after** enhanced protocol encoding.
|
||||
Current code manually maps Ctrl plus certain scancodes to bytes `0x01` etc. Move this child-facing
|
||||
logic from `TerminalDisplay` into the worker-side encoder/fallback, after enhanced protocol
|
||||
encoding.
|
||||
|
||||
Reason:
|
||||
|
||||
@@ -1564,10 +1575,12 @@ Do not leave the Ctrl block before the enhanced encoder.
|
||||
Distinguish:
|
||||
|
||||
- Child output is parsed by `TerminalEmulator`.
|
||||
- Terminal responses and user input are written to the PTY.
|
||||
- Terminal responses and semantic input commands are encoded on the worker and written to the PTY.
|
||||
- Renderer output must never contain protocol responses.
|
||||
- `ttywrite(..., may_echo=0)` is likely correct for terminal-generated responses.
|
||||
- User key input should maintain current `may_echo` semantics, likely `1`.
|
||||
- UI code must never call `TerminalEmulator::ttywrite()` directly or base encoding on a potentially
|
||||
stale snapshot.
|
||||
|
||||
Audit current uses:
|
||||
|
||||
@@ -1703,20 +1716,23 @@ Enter -> 0d
|
||||
Ctrl+Enter -> 0d // current legacy behavior retained
|
||||
```
|
||||
|
||||
With disambiguation:
|
||||
With disambiguation but without report-all:
|
||||
|
||||
```text
|
||||
Enter -> legacy CR, unless spec requires encoded form
|
||||
Ctrl+Enter -> 1b 5b 31 33 3b 35 75
|
||||
Shift+Enter -> 1b 5b 31 33 3b 32 75
|
||||
Alt+Enter -> protocol form required by spec
|
||||
Ctrl+Shift+Enter -> protocol form with modifier 6
|
||||
Enter -> legacy CR
|
||||
Ctrl+Enter -> legacy CR
|
||||
Shift+Enter -> legacy CR
|
||||
Alt+Enter -> legacy ESC CR
|
||||
Ctrl+Shift+Enter -> legacy CR
|
||||
```
|
||||
|
||||
With report-all:
|
||||
|
||||
```text
|
||||
Enter -> enhanced form
|
||||
Enter -> enhanced form
|
||||
Ctrl+Enter -> 1b 5b 31 33 3b 35 75
|
||||
Shift+Enter -> 1b 5b 31 33 3b 32 75
|
||||
Ctrl+Shift+Enter -> protocol form with modifier 6
|
||||
```
|
||||
|
||||
With event types:
|
||||
@@ -1817,10 +1833,10 @@ Enter -> 0d
|
||||
Ctrl+Enter -> 0d
|
||||
```
|
||||
|
||||
Use a helper to activate flags:
|
||||
Use a helper to activate report-all (which implies disambiguation):
|
||||
|
||||
```bash
|
||||
printf '\033[>1u'
|
||||
printf '\033[>8u'
|
||||
```
|
||||
|
||||
Then press Ctrl+Enter and verify:
|
||||
@@ -1850,23 +1866,26 @@ ESC [ ? u
|
||||
and read:
|
||||
|
||||
```text
|
||||
ESC [ ? <supported-mask> u
|
||||
ESC [ ? <current-flags> u
|
||||
```
|
||||
|
||||
## 21.3 Codex CLI
|
||||
|
||||
1. Build and run eTerm from the modified `develop` branch.
|
||||
2. Start current Codex CLI.
|
||||
3. Configure Codex:
|
||||
2. Start current Codex CLI and capture its push/set sequence.
|
||||
3. Verify Codex requests `REPORT_ALL_KEYS_AS_ESCAPE_CODES` (bit `8`). If it requests only flags
|
||||
`7`, record that standards-compliant `Ctrl+Enter` disambiguation is unavailable and do not make
|
||||
the terminal silently deviate from Kitty.
|
||||
4. Configure Codex:
|
||||
- Enter inserts newline.
|
||||
- Ctrl+Enter submits.
|
||||
4. Type a multiline prompt.
|
||||
5. Press Enter: newline appears.
|
||||
6. Press Ctrl+Enter: message submits.
|
||||
7. Hold/repeat keys to ensure no duplicate or stuck events.
|
||||
8. Exit Codex normally.
|
||||
9. Verify shell input returns to legacy behavior.
|
||||
10. Kill Codex abruptly and verify a restarted shell/process does not inherit stale flags.
|
||||
5. Type a multiline prompt.
|
||||
6. Press Enter: newline appears.
|
||||
7. Press Ctrl+Enter: message submits when report-all is active.
|
||||
8. Hold/repeat keys to ensure no duplicate or stuck events.
|
||||
9. Exit Codex normally.
|
||||
10. Verify shell input returns to legacy behavior.
|
||||
11. Kill Codex abruptly and verify a restarted shell/process does not inherit stale flags.
|
||||
|
||||
## 21.4 Other TUIs
|
||||
|
||||
@@ -1921,25 +1940,23 @@ Use constants and comments.
|
||||
|
||||
# 23. Threading and lifetime
|
||||
|
||||
Determine which thread:
|
||||
The thread model is established:
|
||||
|
||||
- parses PTY output,
|
||||
- handles UI input,
|
||||
- reads active flags.
|
||||
- the `TerminalSession` worker exclusively owns `TerminalEmulator`, PTY reads/writes, parser state,
|
||||
terminal modes, Kitty flags/stacks, pending key/text correlation, and child-input encoding;
|
||||
- the UI thread handles terminal-local shortcuts and enqueues semantic commands;
|
||||
- immutable snapshots are presentation-only and must not drive input encoding;
|
||||
- no emulator accessor, atomic flag mirror, or mutex-protected stack is needed.
|
||||
|
||||
If parser updates and UI reads can occur on different threads, `flags` access is a data race.
|
||||
Typed key and text commands participate in the existing ordered command queue. Add deterministic
|
||||
tests for negotiation and key commands arriving close together. The worker must give already
|
||||
available PTY negotiation a fair bounded parsing opportunity without allowing heavy output to
|
||||
starve input commands. Query/response capability detection is also an ordering barrier: an
|
||||
application cannot observe the Kitty query response before the worker has applied the state that
|
||||
produced it.
|
||||
|
||||
Options:
|
||||
|
||||
1. Confirm all terminal update/input operations run on the main thread.
|
||||
2. If not, use:
|
||||
- atomic active flags,
|
||||
- mutex around stack mutation,
|
||||
- or message passing.
|
||||
|
||||
Stacks are modified rarely; a mutex is acceptable. Do not add atomics without protecting the vectors.
|
||||
|
||||
Document the threading guarantee in code.
|
||||
Reset both screen states in full reset and explicitly in `RestartCommand` so a replacement process
|
||||
cannot inherit flags or pending input correlation from the old child.
|
||||
|
||||
---
|
||||
|
||||
@@ -2019,12 +2036,13 @@ allowing modern TUIs to distinguish modified Enter and other keys.
|
||||
|
||||
Deliverables:
|
||||
|
||||
- Parser tests for private CSI prefixes
|
||||
- Parser tests for private CSI prefixes (**completed**; prefixed Kitty sequences are currently
|
||||
recognized and deliberately ignored without a response)
|
||||
- Legacy byte snapshots
|
||||
- Confirm keydown/textinput ordering
|
||||
- Confirm repeat exposure
|
||||
- Confirm modifier constants
|
||||
- Confirm thread model
|
||||
- Confirm thread model (**completed**; emulator and protocol state are worker-owned)
|
||||
|
||||
No functional change.
|
||||
|
||||
@@ -2038,6 +2056,7 @@ Deliverables:
|
||||
- Push/pop/set
|
||||
- Reset behavior
|
||||
- Unit tests
|
||||
- Typed semantic key/text `TerminalSession` commands, initially using legacy worker-side encoding
|
||||
|
||||
Still no enhanced key emission.
|
||||
|
||||
@@ -2048,7 +2067,8 @@ Deliverables:
|
||||
- Encoder framework
|
||||
- Enter, Tab, Escape, Backspace
|
||||
- Modified key CSI-u emission
|
||||
- Ctrl+Enter works in Crossterm/Codex
|
||||
- Modified ASCII/Escape disambiguation works in Crossterm
|
||||
- Ctrl+Enter remains legacy unless report-all is active
|
||||
- Legacy fallback intact
|
||||
|
||||
This phase can be merged independently if desired.
|
||||
@@ -2121,9 +2141,10 @@ The implementation is complete only when all of the following are true.
|
||||
## Encoding
|
||||
|
||||
- [ ] Plain legacy behavior is unchanged with flags zero.
|
||||
- [ ] Ctrl+Enter is `CSI 13;5u` when disambiguation is active.
|
||||
- [ ] Shift+Enter is distinguishable.
|
||||
- [ ] Modified Escape, Tab, and Backspace are distinguishable.
|
||||
- [ ] Ctrl+Enter is `CSI 13;5u` when report-all is active.
|
||||
- [ ] Shift+Enter is distinguishable when report-all is active.
|
||||
- [ ] Modified Escape is distinguishable under disambiguation; modified Tab and Backspace are
|
||||
distinguishable when report-all is active.
|
||||
- [ ] Special/function keys follow official Kitty encoding.
|
||||
- [ ] Printable Unicode keys work in report-all mode.
|
||||
- [ ] No duplicate text-input events occur.
|
||||
@@ -2133,7 +2154,8 @@ The implementation is complete only when all of the following are true.
|
||||
## Integration
|
||||
|
||||
- [ ] Crossterm decodes the events with correct code/modifier/kind.
|
||||
- [ ] Codex can bind Ctrl+Enter separately from Enter.
|
||||
- [ ] Codex can bind Ctrl+Enter separately from Enter when it requests report-all, or any deliberate
|
||||
compatibility deviation is separately designed and documented.
|
||||
- [ ] Shell, Vim/Neovim, and existing terminal use remain unaffected when protocol is inactive.
|
||||
- [ ] Abrupt child termination does not leave stale protocol state for a replacement process.
|
||||
|
||||
@@ -2191,16 +2213,33 @@ KittyKeyboardState& activeKeyboardState();
|
||||
const KittyKeyboardState& activeKeyboardState() const;
|
||||
|
||||
bool handleKittyKeyboardProtocol();
|
||||
void replyKittyKeyboardProtocolSupport();
|
||||
void replyKittyKeyboardProtocolState();
|
||||
void resetKittyKeyboardProtocol();
|
||||
```
|
||||
|
||||
Public:
|
||||
No public emulator accessor is added. State and stack access remain worker-only.
|
||||
|
||||
## `src/modules/eterm/include/eterm/terminal/terminalsession.hpp`
|
||||
|
||||
Add semantic event data and typed commands:
|
||||
|
||||
```cpp
|
||||
std::uint32_t getKeyboardEnhancementFlags() const;
|
||||
void keyDown( TerminalKeyEvent event );
|
||||
void keyUp( TerminalKeyEvent event );
|
||||
void textInput( TerminalTextInput event );
|
||||
|
||||
struct KeyDownCommand { TerminalKeyEvent event; };
|
||||
struct KeyUpCommand { TerminalKeyEvent event; };
|
||||
struct TextInputCommand { TerminalTextInput event; };
|
||||
```
|
||||
|
||||
Add them to the ordered command variant.
|
||||
|
||||
## `src/modules/eterm/src/eterm/terminal/terminalsession.cpp`
|
||||
|
||||
Handle semantic input on the worker. Read the active Kitty state and current emulator modes there,
|
||||
perform key/text correlation, encode either Kitty or legacy bytes, and call `ttywrite()`.
|
||||
|
||||
## `src/modules/eterm/src/eterm/terminal/terminalemulator.cpp`
|
||||
|
||||
Modify:
|
||||
@@ -2213,7 +2252,8 @@ Modify:
|
||||
- terminal-generated query response
|
||||
- optional debug tracing
|
||||
|
||||
Remove the current no-op placeholder only after replacing it with real handling.
|
||||
Replace the current prefixed-`u` no-op only after negotiation state and enhanced input encoding are
|
||||
implemented together.
|
||||
|
||||
## `src/modules/eterm/include/eterm/terminal/terminaldisplay.hpp`
|
||||
|
||||
@@ -2221,20 +2261,18 @@ Add:
|
||||
|
||||
- `onKeyUp`
|
||||
- repeat parameter or event kind
|
||||
- pending/suppressed text state
|
||||
- locally consumed key tracking
|
||||
- helper declarations for enhanced and legacy paths
|
||||
- helper declarations for terminal-local shortcuts
|
||||
|
||||
## `src/modules/eterm/src/eterm/terminal/terminaldisplay.cpp`
|
||||
|
||||
Modify:
|
||||
|
||||
- place enhanced encoding before Ctrl-letter legacy handling
|
||||
- retain terminal shortcut priority
|
||||
- add key-up behavior
|
||||
- add repeat behavior
|
||||
- add text-input correlation
|
||||
- preserve legacy map unchanged as fallback
|
||||
- enqueue semantic key/text commands instead of encoded bytes
|
||||
- move child-facing Ctrl conversion and the legacy map into the worker-side fallback
|
||||
|
||||
## `src/modules/eterm/src/eterm/ui/uiterminal.cpp`
|
||||
|
||||
@@ -2264,12 +2302,12 @@ Before submitting:
|
||||
8. Verify release events for locally consumed shortcuts are suppressed.
|
||||
9. Verify stack depth and pop count are bounded.
|
||||
10. Verify unsupported bits are masked.
|
||||
11. Verify query response mask matches actual implementation.
|
||||
11. Verify the query returns current effective flags, not the supported mask.
|
||||
12. Verify Unicode scalar validation.
|
||||
13. Verify AltGr/IME behavior.
|
||||
14. Verify legacy snapshots with flags zero.
|
||||
15. Verify Crossterm key dump.
|
||||
16. Verify Codex Ctrl+Enter.
|
||||
16. Verify Codex's requested flags before expecting Ctrl+Enter to be distinct.
|
||||
17. Verify process restart clears stale state.
|
||||
18. Run formatter and full test suite.
|
||||
|
||||
@@ -2280,7 +2318,7 @@ Before submitting:
|
||||
A very small patch can solve only the immediate issue:
|
||||
|
||||
```cpp
|
||||
if ( flags & DISAMBIGUATE_ESCAPE_CODES &&
|
||||
if ( flags & REPORT_ALL_KEYS_AS_ESCAPE_CODES &&
|
||||
keyCode == KEY_RETURN &&
|
||||
mod & KEYMOD_CTRL ) {
|
||||
ttywrite( "\033[13;5u", 7, 1 );
|
||||
@@ -2288,7 +2326,9 @@ if ( flags & DISAMBIGUATE_ESCAPE_CODES &&
|
||||
}
|
||||
```
|
||||
|
||||
This is **not** the requested final implementation. It may be useful as a temporary proof of concept, but it is incomplete because it omits:
|
||||
This is **not** the requested final implementation. It is shown only as an illustration of the
|
||||
report-all requirement and must not be added on the UI thread. A production implementation also
|
||||
cannot omit:
|
||||
|
||||
- capability query,
|
||||
- push/pop stack,
|
||||
@@ -2316,13 +2356,13 @@ ESC [ ? u
|
||||
eTerm replies:
|
||||
|
||||
```text
|
||||
ESC [ ? 31 u
|
||||
ESC [ ? 0 u
|
||||
```
|
||||
|
||||
Application sends:
|
||||
|
||||
```text
|
||||
ESC [ > 7 u
|
||||
ESC [ > 15 u
|
||||
```
|
||||
|
||||
eTerm stores prior flags on the current screen’s stack and activates:
|
||||
@@ -2331,6 +2371,7 @@ eTerm stores prior flags on the current screen’s stack and activates:
|
||||
DISAMBIGUATE_ESCAPE_CODES
|
||||
REPORT_EVENT_TYPES
|
||||
REPORT_ALTERNATE_KEYS
|
||||
REPORT_ALL_KEYS_AS_ESCAPE_CODES
|
||||
```
|
||||
|
||||
User presses Ctrl+Enter.
|
||||
@@ -2364,7 +2405,8 @@ KeyModifiers::CONTROL
|
||||
KeyEventKind::Press
|
||||
```
|
||||
|
||||
Codex matches its Ctrl+Enter submit binding.
|
||||
An application such as Codex can match its Ctrl+Enter submit binding when it requests report-all.
|
||||
If it pushes only flags `7`, Enter remains in its legacy form by design.
|
||||
|
||||
Application exits cleanly and sends:
|
||||
|
||||
|
||||
@@ -356,6 +356,7 @@ class TerminalDisplay {
|
||||
VertexBufferUniquePtr mVBForeground;
|
||||
std::vector<VertexBufferUniquePtr> mVBStyles;
|
||||
TerminalColorScheme mColorScheme;
|
||||
TerminalColorScheme mInitialColorScheme;
|
||||
Uint32 mQuadVertex{ 6 };
|
||||
Primitives mPrimitives;
|
||||
Vector2u mCurGridPos;
|
||||
|
||||
@@ -55,7 +55,7 @@ using namespace eterm::System;
|
||||
namespace eterm { namespace Terminal {
|
||||
|
||||
constexpr int ESC_BUF_SIZ = 512;
|
||||
constexpr int ESC_ARG_SIZ = 16;
|
||||
constexpr int ESC_ARG_SIZ = 32;
|
||||
constexpr int STR_BUF_SIZ = ESC_BUF_SIZ;
|
||||
constexpr int STR_ARG_SIZ = ESC_ARG_SIZ;
|
||||
|
||||
@@ -104,6 +104,9 @@ struct CSIEscape {
|
||||
size_t len; /* raw string length */
|
||||
char priv;
|
||||
int arg[ESC_ARG_SIZ];
|
||||
/* Separator following each argument. ECMA-48 uses ';' between parameters and ':'
|
||||
* between subparameters, so preserving it is required for modern SGR colors. */
|
||||
char sep[ESC_ARG_SIZ];
|
||||
int narg; /* nb of args */
|
||||
char mode[2];
|
||||
};
|
||||
@@ -251,6 +254,12 @@ class TerminalEmulator final {
|
||||
/** Worker-only maximum interval between snapshots while PTY reads remain saturated. */
|
||||
void setPresentationInterval( Time interval );
|
||||
|
||||
/** Set the user-configured cursor style restored by DECSCUSR parameter 7. */
|
||||
void setDefaultCursorMode( TerminalCursorMode mode );
|
||||
|
||||
/** Worker-only notification that the display palette changed. */
|
||||
void notifyColorSchemeChanged();
|
||||
|
||||
Vector2i getSize() const;
|
||||
|
||||
System::IProcess* getProcess() const;
|
||||
@@ -285,6 +294,7 @@ class TerminalEmulator final {
|
||||
bool mAllDirty{ true };
|
||||
Clock mPresentationClock;
|
||||
Time mPresentationInterval{ Microseconds( 1000000.0 / 60.0 ) };
|
||||
Clock mSynchronizedUpdateClock;
|
||||
bool mAllowMemoryTrimnming{ false };
|
||||
int mExitCode;
|
||||
|
||||
@@ -305,6 +315,9 @@ class TerminalEmulator final {
|
||||
|
||||
int mAllowAltScreen;
|
||||
int mAllowWindowOps;
|
||||
TerminalCursorMode mDefaultCursorMode{ SteadyUnderline };
|
||||
bool mColorSchemeNotifications{ false };
|
||||
int mColorScheme{ 0 };
|
||||
|
||||
std::string mCurrentWorkingDirectory;
|
||||
Vector2i mLastMousePosition{ -1, -1 };
|
||||
@@ -362,7 +375,7 @@ class TerminalEmulator final {
|
||||
void historyStealPush( Line* lineSlot, int col );
|
||||
void historyReflow( int old_col, int new_col );
|
||||
void historyPopToScreen( int loaded, int col );
|
||||
void tsetattr( int*, int );
|
||||
void tsetattr( int*, int, const char* );
|
||||
void tsetchar( Rune, TerminalGlyph*, int, int );
|
||||
void tsetdirt( int, int );
|
||||
void tsetscroll( int, int );
|
||||
@@ -373,7 +386,7 @@ class TerminalEmulator final {
|
||||
void tcontrolcode( uchar );
|
||||
void tdectest( char );
|
||||
void tdefutf8( char );
|
||||
int32_t tdefcolor( int*, int*, int );
|
||||
int32_t tdefcolor( int*, const char*, int*, int );
|
||||
void tdeftran( char );
|
||||
void tstrsequence( uchar );
|
||||
|
||||
@@ -413,6 +426,8 @@ class TerminalEmulator final {
|
||||
int xgetcolor( int x, unsigned char* r, unsigned char* g, unsigned char* b );
|
||||
void osc_color_response( int num, int index, int is_osc4 );
|
||||
void handleDeviceAttributes();
|
||||
int colorScheme();
|
||||
void reportColorScheme();
|
||||
|
||||
void trimMemory();
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ enum TerminalWinMode {
|
||||
MODE_MOUSEMANY = 1 << 15,
|
||||
MODE_BRCKTPASTE = 1 << 16,
|
||||
MODE_NUMLOCK = 1 << 17,
|
||||
MODE_ALTSCRROLL = 1 << 18,
|
||||
MODE_MOUSE = MODE_MOUSEBTN | MODE_MOUSEMOTION | MODE_MOUSEX10 | MODE_MOUSEMANY,
|
||||
};
|
||||
|
||||
|
||||
@@ -450,9 +450,8 @@ std::shared_ptr<TerminalDisplay> TerminalDisplay::create(
|
||||
std::shared_ptr<TerminalDisplay> terminal = std::shared_ptr<TerminalDisplay>(
|
||||
new TerminalDisplay( window, font, fontSize, pixelsSize, useFrameBuffer ) );
|
||||
|
||||
terminal->mSession =
|
||||
TerminalSession::create( std::move( pseudoTerminal ), std::move( process ), historySize,
|
||||
terminal->makeColorPalette() );
|
||||
terminal->mSession = TerminalSession::create( std::move( pseudoTerminal ), std::move( process ),
|
||||
historySize, terminal->makeColorPalette() );
|
||||
if ( !terminal->mSession ) {
|
||||
if ( freeProcessFactory )
|
||||
eeSAFE_DELETE( processFactory );
|
||||
@@ -484,7 +483,8 @@ TerminalDisplay::TerminalDisplay( EE::Window::Window* window, Font* font, const
|
||||
mFontSize( fontSize ),
|
||||
mSize( pixelsSize ),
|
||||
mUseFrameBuffer( useFrameBuffer ),
|
||||
mColorScheme( TerminalColorScheme::getDefault() ) {
|
||||
mColorScheme( TerminalColorScheme::getDefault() ),
|
||||
mInitialColorScheme( mColorScheme ) {
|
||||
TerminalGlyph defaultGlyph;
|
||||
defaultGlyph.mode = ATTR_INVISIBLE;
|
||||
mCursorGlyph = defaultGlyph;
|
||||
@@ -504,26 +504,33 @@ TerminalDisplay::TerminalDisplay( EE::Window::Window* window, Font* font, const
|
||||
}
|
||||
|
||||
void TerminalDisplay::resetColors() {
|
||||
mColorScheme = mInitialColorScheme;
|
||||
for ( Uint32 i = 0; i < eeARRAY_SIZE( colormapped ); i++ )
|
||||
resetColor( i, i < mColorScheme.getPaletteSize()
|
||||
? mColorScheme.getPaletteIndex( i ).toHexString().c_str()
|
||||
resetColor( i, i < mInitialColorScheme.getPaletteSize()
|
||||
? mInitialColorScheme.getPaletteIndex( i ).toHexString().c_str()
|
||||
: nullptr );
|
||||
}
|
||||
|
||||
int TerminalDisplay::resetColor( const Uint32& index, const char* name ) {
|
||||
if ( !name ) {
|
||||
if ( index < mColors.size() ) {
|
||||
Color col = 0x000000FF;
|
||||
|
||||
if ( index < 256 )
|
||||
col = colormapped[index];
|
||||
const Color col = index < mInitialColorScheme.getPaletteSize()
|
||||
? mInitialColorScheme.getPaletteIndex( index )
|
||||
: colormapped[index];
|
||||
|
||||
mColors[index] = col;
|
||||
mColorScheme.setPaletteIndex( index, col );
|
||||
return 0;
|
||||
} else if ( index == 256 || index == 257 ) {
|
||||
mColorScheme.setCursor( mInitialColorScheme.getCursor() );
|
||||
return 0;
|
||||
} else if ( index == 258 ) {
|
||||
mColorScheme.setForeground( mInitialColorScheme.getForeground() );
|
||||
return 0;
|
||||
} else if ( index == 259 ) {
|
||||
mColorScheme.setBackground( mInitialColorScheme.getBackground() );
|
||||
return 0;
|
||||
}
|
||||
// Reset to default for 256, 257, 258, 259 is not well defined here without original
|
||||
// defaults
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -549,10 +556,7 @@ int TerminalDisplay::resetColor( const Uint32& index, const char* name ) {
|
||||
}
|
||||
}
|
||||
} else if ( String::iequals( "default", name ) ) {
|
||||
unsigned char r, g, b;
|
||||
getColor( index, &r, &g, &b );
|
||||
col = Color( r, g, b, 255 );
|
||||
colorParsed = true;
|
||||
return resetColor( index, nullptr );
|
||||
} else if ( Color::isColorString( std::string_view{ name }, true ) ) {
|
||||
col = Color::fromString( name );
|
||||
colorParsed = true;
|
||||
@@ -677,6 +681,7 @@ const TerminalColorScheme& TerminalDisplay::getColorScheme() const {
|
||||
}
|
||||
|
||||
void TerminalDisplay::setColorScheme( const TerminalColorScheme& colorScheme ) {
|
||||
mInitialColorScheme = colorScheme;
|
||||
mColorScheme = colorScheme;
|
||||
resetColors();
|
||||
if ( mSession )
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include <eepp/system/cpu.hpp>
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/system/sys.hpp>
|
||||
#include <eepp/version.hpp>
|
||||
|
||||
using namespace EE::Network;
|
||||
using namespace EE::System;
|
||||
@@ -76,6 +77,52 @@ extern "C" {
|
||||
|
||||
namespace eterm { namespace Terminal {
|
||||
|
||||
namespace {
|
||||
|
||||
bool terminalDiagnosticsEnabled() {
|
||||
#ifdef EE_DEBUG
|
||||
return true;
|
||||
#else
|
||||
// Release builds stay quiet unless diagnostics are explicitly requested. This is cached because
|
||||
// malformed or unsupported sequences can otherwise hit this path for every rendered cell.
|
||||
static const char* setting = getenv( "ETERM_LOG_ERRORS" );
|
||||
static const bool enabled =
|
||||
setting != nullptr && setting[0] != '\0' && strcmp( setting, "0" ) != 0;
|
||||
return enabled;
|
||||
#endif
|
||||
}
|
||||
|
||||
void terminalDiagnostic( const char* format, ... ) {
|
||||
if ( !terminalDiagnosticsEnabled() )
|
||||
return;
|
||||
va_list args;
|
||||
va_start( args, format );
|
||||
vfprintf( stderr, format, args );
|
||||
va_end( args );
|
||||
}
|
||||
|
||||
TerminalCursorMode blinkingCursorVariant( TerminalCursorMode mode ) {
|
||||
switch ( mode ) {
|
||||
case BlinkUnderline:
|
||||
case SteadyUnderline:
|
||||
return BlinkUnderline;
|
||||
case BlinkBar:
|
||||
case SteadyBar:
|
||||
return BlinkBar;
|
||||
case BlinkingBlock:
|
||||
case BlinkingBlockDefault:
|
||||
case SteadyBlock:
|
||||
return BlinkingBlock;
|
||||
case StExtension:
|
||||
return StExtension;
|
||||
case TerminalCursorMode::MAX_CURSOR:
|
||||
return BlinkingBlock;
|
||||
}
|
||||
return BlinkingBlock;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#if defined( EE_ARCH_X86_64 )
|
||||
#if defined( __GNUC__ ) || defined( __clang__ )
|
||||
__attribute__( ( target( "avx2" ) ) )
|
||||
@@ -848,6 +895,17 @@ void TerminalEmulator::setPresentationInterval( Time interval ) {
|
||||
mPresentationInterval = interval > Time::Zero ? interval : Microseconds( 1000000.0 / 60.0 );
|
||||
}
|
||||
|
||||
void TerminalEmulator::setDefaultCursorMode( TerminalCursorMode mode ) {
|
||||
mDefaultCursorMode = mode;
|
||||
}
|
||||
|
||||
void TerminalEmulator::notifyColorSchemeChanged() {
|
||||
const int scheme = colorScheme();
|
||||
if ( mColorSchemeNotifications && mColorScheme != 0 && scheme != mColorScheme )
|
||||
reportColorScheme();
|
||||
mColorScheme = scheme;
|
||||
}
|
||||
|
||||
Vector2i TerminalEmulator::getSize() const {
|
||||
return { mTerm.col, mTerm.row };
|
||||
}
|
||||
@@ -962,6 +1020,8 @@ void TerminalEmulator::tcursor( int mode ) {
|
||||
void TerminalEmulator::treset( void ) {
|
||||
uint i;
|
||||
|
||||
mColorSchemeNotifications = false;
|
||||
mTerm.is_syncing = false;
|
||||
mTerm.c = TerminalCursor{};
|
||||
mTerm.c.attr = TerminalGlyph{};
|
||||
mTerm.c.attr.u = ' ';
|
||||
@@ -990,9 +1050,13 @@ void TerminalEmulator::treset( void ) {
|
||||
|
||||
xsetmode( 0, MODE_MOUSE | MODE_MOUSESGR | MODE_APPKEYPAD | MODE_APPCURSOR | MODE_FOCUS |
|
||||
MODE_BRCKTPASTE | MODE_MOUSEX10 | MODE_MOUSEMANY );
|
||||
// Preserve eterm's established behavior and xterm's default alternateScroll resource.
|
||||
xsetmode( 1, MODE_ALTSCRROLL );
|
||||
auto dpy = mDpy.lock();
|
||||
if ( dpy )
|
||||
if ( dpy ) {
|
||||
dpy->setMode( MODE_VISIBLE, 1 );
|
||||
dpy->setCursorMode( mDefaultCursorMode );
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalEmulator::tnew( int col, int row, size_t historySize ) {
|
||||
@@ -1419,27 +1483,25 @@ void TerminalEmulator::tnewline( int first_col ) {
|
||||
void TerminalEmulator::csiparse( void ) {
|
||||
char *p = mCsiescseq.buf, *np;
|
||||
long int v;
|
||||
int sep = ';'; /* colon or semi-colon, but not both */
|
||||
|
||||
mCsiescseq.narg = 0;
|
||||
if ( *p == '?' ) {
|
||||
mCsiescseq.priv = 1;
|
||||
if ( *p == '<' || *p == '=' || *p == '>' || *p == '?' ) {
|
||||
mCsiescseq.priv = *p;
|
||||
p++;
|
||||
}
|
||||
|
||||
mCsiescseq.buf[mCsiescseq.len] = '\0';
|
||||
while ( p < mCsiescseq.buf + mCsiescseq.len ) {
|
||||
while ( p < mCsiescseq.buf + mCsiescseq.len && mCsiescseq.narg < ESC_ARG_SIZ ) {
|
||||
np = NULL;
|
||||
v = strtol( p, &np, 10 );
|
||||
if ( np == p )
|
||||
v = 0;
|
||||
if ( v == LONG_MAX || v == LONG_MIN )
|
||||
v = -1;
|
||||
mCsiescseq.arg[mCsiescseq.narg++] = v;
|
||||
mCsiescseq.arg[mCsiescseq.narg] = v;
|
||||
p = np;
|
||||
if ( sep == ';' && *p == ':' )
|
||||
sep = ':'; /* allow override to colon once */
|
||||
if ( *p != sep || mCsiescseq.narg == ESC_ARG_SIZ )
|
||||
mCsiescseq.sep[mCsiescseq.narg++] = *p == ';' || *p == ':' ? *p : '\0';
|
||||
if ( *p != ';' && *p != ':' )
|
||||
break;
|
||||
p++;
|
||||
}
|
||||
@@ -1596,33 +1658,63 @@ void TerminalEmulator::tdeleteline( int n ) {
|
||||
tscrollup( mTerm.c.y, n, 0 );
|
||||
}
|
||||
|
||||
int32_t TerminalEmulator::tdefcolor( int* attr, int* npar, int l ) {
|
||||
int32_t TerminalEmulator::tdefcolor( int* attr, const char* separators, int* npar, int l ) {
|
||||
int32_t idx = -1;
|
||||
uint r, g, b;
|
||||
if ( !attr || !npar || l < 0 || l > ESC_ARG_SIZ || *npar < 0 || *npar >= l ) {
|
||||
terminalDiagnostic( "erresc(color): invalid parameter index\n" );
|
||||
return idx;
|
||||
}
|
||||
const bool subparameters = separators && separators[*npar] == ':';
|
||||
const int selector = *npar + 1;
|
||||
|
||||
switch ( attr[*npar + 1] ) {
|
||||
if ( selector >= l ) {
|
||||
terminalDiagnostic( "erresc(color): missing color type\n" );
|
||||
return idx;
|
||||
}
|
||||
|
||||
switch ( attr[selector] ) {
|
||||
case 2: /* direct color in RGB space */
|
||||
if ( *npar + 4 >= l ) {
|
||||
fprintf( stderr, "erresc(38): Incorrect number of parameters (%d)\n", *npar );
|
||||
if ( subparameters ) {
|
||||
int end = selector;
|
||||
while ( end < l - 1 && separators[end] == ':' )
|
||||
++end;
|
||||
const int componentCount = end - selector;
|
||||
if ( componentCount != 3 && componentCount != 4 ) {
|
||||
terminalDiagnostic( "erresc(color): invalid RGB subparameter count %d\n",
|
||||
componentCount );
|
||||
*npar = end;
|
||||
break;
|
||||
}
|
||||
const int rgb = selector + ( componentCount == 4 ? 2 : 1 );
|
||||
r = attr[rgb];
|
||||
g = attr[rgb + 1];
|
||||
b = attr[rgb + 2];
|
||||
*npar = end;
|
||||
} else if ( *npar + 4 < l ) {
|
||||
r = attr[*npar + 2];
|
||||
g = attr[*npar + 3];
|
||||
b = attr[*npar + 4];
|
||||
*npar += 4;
|
||||
} else {
|
||||
terminalDiagnostic( "erresc(color): incorrect number of RGB parameters (%d)\n",
|
||||
*npar );
|
||||
break;
|
||||
}
|
||||
r = attr[*npar + 2];
|
||||
g = attr[*npar + 3];
|
||||
b = attr[*npar + 4];
|
||||
*npar += 4;
|
||||
if ( !BETWEEN( r, 0, 255 ) || !BETWEEN( g, 0, 255 ) || !BETWEEN( b, 0, 255 ) )
|
||||
fprintf( stderr, "erresc: bad rgb color (%u,%u,%u)\n", r, g, b );
|
||||
terminalDiagnostic( "erresc: bad rgb color (%u,%u,%u)\n", r, g, b );
|
||||
else
|
||||
idx = TRUECOLOR( r, g, b );
|
||||
break;
|
||||
case 5: /* indexed color */
|
||||
if ( *npar + 2 >= l ) {
|
||||
fprintf( stderr, "erresc(38): Incorrect number of parameters (%d)\n", *npar );
|
||||
terminalDiagnostic( "erresc(color): incorrect number of indexed parameters (%d)\n",
|
||||
*npar );
|
||||
break;
|
||||
}
|
||||
*npar += 2;
|
||||
if ( !BETWEEN( attr[*npar], 0, 255 ) )
|
||||
fprintf( stderr, "erresc: bad fgcolor %d\n", attr[*npar] );
|
||||
terminalDiagnostic( "erresc: bad indexed color %d\n", attr[*npar] );
|
||||
else
|
||||
idx = attr[*npar];
|
||||
break;
|
||||
@@ -1631,14 +1723,18 @@ int32_t TerminalEmulator::tdefcolor( int* attr, int* npar, int l ) {
|
||||
case 3: /* direct color in CMY space */
|
||||
case 4: /* direct color in CMYK space */
|
||||
default:
|
||||
fprintf( stderr, "erresc(38): gfx attr %d unknown\n", attr[*npar] );
|
||||
terminalDiagnostic( "erresc(color): color type %d unknown\n", attr[selector] );
|
||||
if ( subparameters ) {
|
||||
while ( *npar < l - 1 && separators[*npar] == ':' )
|
||||
++*npar;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
void TerminalEmulator::tsetattr( int* attr, int l ) {
|
||||
void TerminalEmulator::tsetattr( int* attr, int l, const char* separators ) {
|
||||
// Check if this is a private sequence (should be ignored for SGR)
|
||||
// Private sequences start with '?' and should not affect text attributes
|
||||
if ( mCsiescseq.priv ) {
|
||||
@@ -1706,14 +1802,14 @@ void TerminalEmulator::tsetattr( int* attr, int l ) {
|
||||
mTerm.c.attr.mode &= ~ATTR_STRUCK;
|
||||
break;
|
||||
case 38:
|
||||
if ( ( idx = tdefcolor( attr, &i, l ) ) >= 0 )
|
||||
if ( ( idx = tdefcolor( attr, separators, &i, l ) ) >= 0 )
|
||||
mTerm.c.attr.fg = idx;
|
||||
break;
|
||||
case 39: /* set foreground color to default */
|
||||
mTerm.c.attr.fg = mDefaultFg;
|
||||
break;
|
||||
case 48:
|
||||
if ( ( idx = tdefcolor( attr, &i, l ) ) >= 0 )
|
||||
if ( ( idx = tdefcolor( attr, separators, &i, l ) ) >= 0 )
|
||||
mTerm.c.attr.bg = idx;
|
||||
break;
|
||||
case 49: /* set background color to default */
|
||||
@@ -1723,7 +1819,9 @@ void TerminalEmulator::tsetattr( int* attr, int l ) {
|
||||
/* This starts a sequence to change the color of
|
||||
* "underline" pixels. We don't support that and
|
||||
* instead eat up a following "5;n" or "2;r;g;b". */
|
||||
tdefcolor( attr, &i, l );
|
||||
tdefcolor( attr, separators, &i, l );
|
||||
break;
|
||||
case 59: /* reset underline color (unsupported, therefore a no-op) */
|
||||
break;
|
||||
default:
|
||||
if ( BETWEEN( attr[i], 30, 37 ) ) {
|
||||
@@ -1735,8 +1833,9 @@ void TerminalEmulator::tsetattr( int* attr, int l ) {
|
||||
} else if ( BETWEEN( attr[i], 100, 107 ) ) {
|
||||
mTerm.c.attr.bg = attr[i] - 100 + 8;
|
||||
} else {
|
||||
fprintf( stderr, "erresc(default): gfx attr %d unknown\n", attr[i] );
|
||||
csidump();
|
||||
terminalDiagnostic( "erresc(default): gfx attr %d unknown\n", attr[i] );
|
||||
if ( terminalDiagnosticsEnabled() )
|
||||
csidump();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1817,6 +1916,9 @@ void TerminalEmulator::tsetmode( int priv, int set, int* args, int narg ) {
|
||||
case 1006: /* 1006: extended reporting mode */
|
||||
xsetmode( set, MODE_MOUSESGR );
|
||||
break;
|
||||
case 1007: /* wheel sends cursor keys on the alternate screen */
|
||||
xsetmode( set, MODE_ALTSCRROLL );
|
||||
break;
|
||||
case 1034:
|
||||
xsetmode( set, MODE_8BIT );
|
||||
break;
|
||||
@@ -1856,19 +1958,23 @@ void TerminalEmulator::tsetmode( int priv, int set, int* args, int narg ) {
|
||||
case 1039: /* ESC to Meta (not implemented) */
|
||||
break;
|
||||
case 2026: {
|
||||
// IGNORE DECSET/DECRST 2026 for sync updates?
|
||||
// (https://codeberg.org/dnkl/foot/pulls/461/files)
|
||||
// mTerm.is_syncing = ( set == 1 );
|
||||
/* if ( !mTerm.is_syncing ) {
|
||||
// When syncing ends, we must perform the deferred draw
|
||||
if ( set ) {
|
||||
mTerm.is_syncing = true;
|
||||
mSynchronizedUpdateClock.restart();
|
||||
} else if ( mTerm.is_syncing ) {
|
||||
mTerm.is_syncing = false;
|
||||
// Publish the complete frame immediately instead of waiting for the next
|
||||
// presentation deadline.
|
||||
draw();
|
||||
} */
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2031: /* Light/dark color-scheme change notifications. */
|
||||
mColorSchemeNotifications = set;
|
||||
mColorScheme = colorScheme();
|
||||
break;
|
||||
default:
|
||||
#ifdef EE_DEBUG
|
||||
fprintf( stderr, "erresc: unknown private set/reset mode %d\n", *args );
|
||||
#endif
|
||||
terminalDiagnostic( "erresc: unknown private set/reset mode %d\n", *args );
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@@ -1888,9 +1994,7 @@ void TerminalEmulator::tsetmode( int priv, int set, int* args, int narg ) {
|
||||
MODBIT( mTerm.mode, set, MODE_CRLF );
|
||||
break;
|
||||
default:
|
||||
#ifdef EE_DEBUG
|
||||
fprintf( stderr, "erresc: unknown set/reset mode %d\n", *args );
|
||||
#endif
|
||||
terminalDiagnostic( "erresc: unknown set/reset mode %d\n", *args );
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1898,7 +2002,13 @@ void TerminalEmulator::tsetmode( int priv, int set, int* args, int narg ) {
|
||||
}
|
||||
|
||||
void TerminalEmulator::handleDeviceAttributes() {
|
||||
if ( mCsiescseq.priv ) {
|
||||
if ( mCsiescseq.priv == '>' ) {
|
||||
char buf[64];
|
||||
const auto version = EE::Version::getVersion();
|
||||
const int revision = version.major * 10000 + version.minor * 100 + version.patch;
|
||||
const int len = snprintf( buf, sizeof( buf ), "\033[>0;%d;0c", revision );
|
||||
ttywrite( buf, len, 0 );
|
||||
} else if ( mCsiescseq.priv == '?' ) {
|
||||
char buf[64];
|
||||
int len;
|
||||
// Private Device Attributes - respond with terminal capabilities
|
||||
@@ -1921,7 +2031,7 @@ void TerminalEmulator::handleDeviceAttributes() {
|
||||
// Unknown private DA query
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
} else if ( !mCsiescseq.priv ) {
|
||||
// Standard DA - respond with VT100 identification
|
||||
ttywrite( vtiden, strlen( vtiden ), 0 );
|
||||
}
|
||||
@@ -1936,10 +2046,8 @@ void TerminalEmulator::csihandle( void ) {
|
||||
switch ( mCsiescseq.mode[0] ) {
|
||||
default:
|
||||
unknown:
|
||||
#ifdef EE_DEBUG
|
||||
fprintf( stderr, "erresc: unknown csi " );
|
||||
terminalDiagnostic( "erresc: unknown csi " );
|
||||
csidump();
|
||||
#endif
|
||||
/* die(""); */
|
||||
break;
|
||||
case '@': /* ICH -- Insert <n> blank char */
|
||||
@@ -2076,7 +2184,9 @@ void TerminalEmulator::csihandle( void ) {
|
||||
tinsertblankline( mCsiescseq.arg[0] );
|
||||
break;
|
||||
case 'l': /* RM -- Reset Mode */
|
||||
tsetmode( mCsiescseq.priv, 0, mCsiescseq.arg, mCsiescseq.narg );
|
||||
if ( mCsiescseq.priv && mCsiescseq.priv != '?' )
|
||||
goto unknown;
|
||||
tsetmode( mCsiescseq.priv == '?', 0, mCsiescseq.arg, mCsiescseq.narg );
|
||||
break;
|
||||
case 'M': /* DL -- Delete <n> lines */
|
||||
DEFAULT( mCsiescseq.arg[0], 1 );
|
||||
@@ -2099,41 +2209,19 @@ void TerminalEmulator::csihandle( void ) {
|
||||
tmoveato( mTerm.c.x, mCsiescseq.arg[0] - 1 );
|
||||
break;
|
||||
case 'h': /* SM -- Set terminal mode */
|
||||
tsetmode( mCsiescseq.priv, 1, mCsiescseq.arg, mCsiescseq.narg );
|
||||
if ( mCsiescseq.priv && mCsiescseq.priv != '?' )
|
||||
goto unknown;
|
||||
tsetmode( mCsiescseq.priv == '?', 1, mCsiescseq.arg, mCsiescseq.narg );
|
||||
break;
|
||||
case 'm': /* SGR -- Terminal attribute (color) */
|
||||
tsetattr( mCsiescseq.arg, mCsiescseq.narg );
|
||||
break;
|
||||
case '>': /* Private sequences */
|
||||
switch ( mCsiescseq.mode[1] ) {
|
||||
case '4': /* Extended underline styles ESC[>4;Nm */
|
||||
// Extended underline styles - fallback to standard underline
|
||||
/* DEFAULT( mCsiescseq.arg[0], 1 );
|
||||
switch ( mCsiescseq.arg[0] ) {
|
||||
case 0: // No underline - fallback to ESC[24m
|
||||
{
|
||||
int fallback_args[] = { 24 }; // Reset underline
|
||||
tsetattr( fallback_args, 1 );
|
||||
} break;
|
||||
case 1: // Straight underline - fallback to ESC[4m
|
||||
case 2: // Double underline
|
||||
case 3: // Curly underline
|
||||
case 4: // Dotted underline
|
||||
case 5: // Dashed underline
|
||||
{
|
||||
int fallback_args[] = { 4 }; // Standard underline
|
||||
tsetattr( fallback_args, 1 );
|
||||
} break;
|
||||
default:
|
||||
goto unknown;
|
||||
} */
|
||||
break;
|
||||
default:
|
||||
goto unknown;
|
||||
}
|
||||
if ( mCsiescseq.priv == '>' )
|
||||
break; // Extended underline styles are not rendered yet.
|
||||
tsetattr( mCsiescseq.arg, mCsiescseq.narg, mCsiescseq.sep );
|
||||
break;
|
||||
case 'n': /* DSR – Device Status Report (cursor position) */
|
||||
if ( mCsiescseq.arg[0] == 6 ) {
|
||||
if ( mCsiescseq.priv == '?' && mCsiescseq.arg[0] == 996 ) {
|
||||
reportColorScheme();
|
||||
} else if ( !mCsiescseq.priv && mCsiescseq.arg[0] == 6 ) {
|
||||
len = snprintf( buf, sizeof( buf ), "\033[%i;%iR", mTerm.c.y + 1, mTerm.c.x + 1 );
|
||||
ttywrite( buf, len, 0 );
|
||||
}
|
||||
@@ -2152,7 +2240,12 @@ void TerminalEmulator::csihandle( void ) {
|
||||
tcursor( CURSOR_SAVE );
|
||||
break;
|
||||
case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
|
||||
if ( mCsiescseq.priv ) {
|
||||
if ( mCsiescseq.priv == '?' || mCsiescseq.priv == '>' || mCsiescseq.priv == '<' ||
|
||||
mCsiescseq.priv == '=' ) {
|
||||
// Kitty keyboard protocol. Remain in legacy mode and do not answer its query: a
|
||||
// response would claim support and require encoding all subsequent key events.
|
||||
break;
|
||||
} else if ( mCsiescseq.priv ) {
|
||||
goto unknown;
|
||||
} else {
|
||||
tcursor( CURSOR_LOAD );
|
||||
@@ -2161,20 +2254,27 @@ void TerminalEmulator::csihandle( void ) {
|
||||
case ' ':
|
||||
switch ( mCsiescseq.mode[1] ) {
|
||||
case 'q': /* DECSCUSR -- Set Cursor Style */
|
||||
if ( mCsiescseq.arg[0] < 0 ||
|
||||
mCsiescseq.arg[0] < TerminalCursorMode::MAX_CURSOR )
|
||||
if ( mCsiescseq.priv || mCsiescseq.arg[0] < 0 ||
|
||||
mCsiescseq.arg[0] >= TerminalCursorMode::StExtension )
|
||||
goto unknown;
|
||||
dpy = mDpy.lock();
|
||||
if ( dpy )
|
||||
dpy->setCursorMode( (TerminalCursorMode)mCsiescseq.arg[0] );
|
||||
if ( dpy ) {
|
||||
const auto mode = static_cast<TerminalCursorMode>( mCsiescseq.arg[0] );
|
||||
dpy->setCursorMode( mode == BlinkingBlock
|
||||
? blinkingCursorVariant( mDefaultCursorMode )
|
||||
: mode );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
goto unknown;
|
||||
}
|
||||
break;
|
||||
case '=': /* Progressive enhancement sequences */
|
||||
/* Keyboard protocol ESC[=Nu */
|
||||
/* Do nothing for the moment */
|
||||
case 'q': /* XTVERSION -- Report terminal name and version */
|
||||
if ( mCsiescseq.priv != '>' || mCsiescseq.arg[0] != 0 )
|
||||
goto unknown;
|
||||
len = snprintf( buf, sizeof( buf ), "\033P>|eterm %s\033\\",
|
||||
EE::Version::getVersionName( false ).c_str() );
|
||||
ttywrite( buf, len, 0 );
|
||||
break;
|
||||
case 't': /* Window manipulation */
|
||||
switch ( mCsiescseq.arg[0] ) {
|
||||
@@ -2199,16 +2299,12 @@ void TerminalEmulator::csihandle( void ) {
|
||||
goto unknown;
|
||||
}
|
||||
break;
|
||||
case '?':
|
||||
/* Private mode queries - ignore or handle appropriately */
|
||||
/* For XTQMODKEYS and similar queries, we should either:
|
||||
1. Ignore completely (do nothing)
|
||||
2. Send a proper response if required */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalEmulator::csidump( void ) {
|
||||
if ( !terminalDiagnosticsEnabled() )
|
||||
return;
|
||||
size_t i;
|
||||
uint c;
|
||||
|
||||
@@ -2283,7 +2379,7 @@ void TerminalEmulator::strhandle( void ) {
|
||||
setClipboard( dec );
|
||||
xfree( dec );
|
||||
} else {
|
||||
fprintf( stderr, "erresc: invalid base64\n" );
|
||||
terminalDiagnostic( "erresc: invalid base64\n" );
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -2299,31 +2395,57 @@ void TerminalEmulator::strhandle( void ) {
|
||||
if ( !strcmp( p, "?" ) ) {
|
||||
osc_color_response( par, osc_table[j].idx, 0 );
|
||||
} else if ( xsetcolorname( osc_table[j].idx, p ) ) {
|
||||
fprintf( stderr, "erresc: invalid %s color: %s\n", osc_table[j].str, p );
|
||||
terminalDiagnostic( "erresc: invalid %s color: %s\n", osc_table[j].str, p );
|
||||
} else {
|
||||
tfulldirt();
|
||||
}
|
||||
return;
|
||||
case 4: /* color set */
|
||||
if ( narg < 3 )
|
||||
break;
|
||||
p = mStrescseq.args[2];
|
||||
/* FALLTHROUGH */
|
||||
case 104: /* color reset, here p = NULL */
|
||||
j = ( narg > 1 ) ? atoi( mStrescseq.args[1] ) : -1;
|
||||
if ( resetColor( j, p ) ) {
|
||||
if ( par == 104 && narg <= 1 )
|
||||
return; /* color reset without parameter */
|
||||
fprintf( stderr, "erresc: invalid color j=%d, p=%s\n", j,
|
||||
p ? p : "(null)" );
|
||||
} else {
|
||||
/*
|
||||
* TODO if defaultbg color is changed, borders
|
||||
* are dirty
|
||||
*/
|
||||
redraw();
|
||||
case 4: { /* set or query palette colors */
|
||||
bool changed = false;
|
||||
for ( int arg = 1; arg + 1 < narg; arg += 2 ) {
|
||||
p = mStrescseq.args[arg + 1];
|
||||
if ( !String::fromString( j, std::string_view{ mStrescseq.args[arg] } ) ||
|
||||
j < 0 ) {
|
||||
terminalDiagnostic( "erresc: invalid OSC 4 color index: %s\n",
|
||||
mStrescseq.args[arg] );
|
||||
continue;
|
||||
}
|
||||
if ( !strcmp( p, "?" ) ) {
|
||||
osc_color_response( j, j, 1 );
|
||||
} else if ( resetColor( j, p ) ) {
|
||||
terminalDiagnostic( "erresc: invalid color j=%d, p=%s\n", j, p );
|
||||
} else {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if ( changed )
|
||||
redraw();
|
||||
return;
|
||||
}
|
||||
case 104: { /* reset palette colors */
|
||||
if ( narg <= 1 ) {
|
||||
loadColors();
|
||||
tfulldirt();
|
||||
return;
|
||||
}
|
||||
bool changed = false;
|
||||
for ( int arg = 1; arg < narg; ++arg ) {
|
||||
if ( !String::fromString( j, std::string_view{ mStrescseq.args[arg] } ) ||
|
||||
j < 0 ) {
|
||||
terminalDiagnostic( "erresc: invalid OSC 104 color index: %s\n",
|
||||
mStrescseq.args[arg] );
|
||||
continue;
|
||||
}
|
||||
if ( resetColor( j, nullptr ) ) {
|
||||
terminalDiagnostic( "erresc: palette color %d not found\n", j );
|
||||
} else {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if ( changed )
|
||||
tfulldirt();
|
||||
return;
|
||||
}
|
||||
case 110: /* reset dynamic VT100 text foreground color */
|
||||
case 111: /* reset dynamic VT100 text background color */
|
||||
case 112: /* reset dynamic text cursor color */
|
||||
@@ -2332,7 +2454,7 @@ void TerminalEmulator::strhandle( void ) {
|
||||
if ( ( j = par - 110 ) < 0 || j >= (int)LEN( osc_table ) )
|
||||
break; /* shouldn't be possible */
|
||||
if ( resetColor( osc_table[j].idx, NULL ) ) {
|
||||
fprintf( stderr, "erresc: %s color not found\n", osc_table[j].str );
|
||||
terminalDiagnostic( "erresc: %s color not found\n", osc_table[j].str );
|
||||
} else {
|
||||
tfulldirt();
|
||||
}
|
||||
@@ -2342,6 +2464,13 @@ void TerminalEmulator::strhandle( void ) {
|
||||
mCurrentWorkingDirectory = URI( mStrescseq.args[1] ).getPath();
|
||||
return;
|
||||
}
|
||||
case 8: /* Hyperlink: OSC 8 ; params ; URI ST */
|
||||
// Hyperlink metadata is not stored in terminal cells yet. Recognize valid open
|
||||
// and close markers so applications can emit OSC 8 without producing
|
||||
// diagnostics.
|
||||
if ( narg >= 3 )
|
||||
return;
|
||||
break;
|
||||
case 133: {
|
||||
if ( narg > 1 ) {
|
||||
j = ( narg > 1 ) ? mStrescseq.args[1][0] : -1;
|
||||
@@ -2388,10 +2517,8 @@ void TerminalEmulator::strhandle( void ) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef EE_DEBUG
|
||||
logError( "erresc: unknown str " );
|
||||
terminalDiagnostic( "erresc: unknown str " );
|
||||
strdump();
|
||||
#endif
|
||||
}
|
||||
|
||||
void TerminalEmulator::strparse( void ) {
|
||||
@@ -2415,6 +2542,8 @@ void TerminalEmulator::strparse( void ) {
|
||||
}
|
||||
|
||||
void TerminalEmulator::strdump( void ) {
|
||||
if ( !terminalDiagnosticsEnabled() )
|
||||
return;
|
||||
size_t i;
|
||||
uint c;
|
||||
|
||||
@@ -2531,7 +2660,7 @@ void TerminalEmulator::tdeftran( char ascii ) {
|
||||
char* p;
|
||||
|
||||
if ( ( p = strchr( cs, ascii ) ) == NULL ) {
|
||||
fprintf( stderr, "esc unhandled charset: ESC ( %c\n", ascii );
|
||||
terminalDiagnostic( "esc unhandled charset: ESC ( %c\n", ascii );
|
||||
} else {
|
||||
mTerm.trantbl[mTerm.icharset] = vcs[p - cs];
|
||||
}
|
||||
@@ -2743,8 +2872,8 @@ int TerminalEmulator::eschandle( uchar ascii ) {
|
||||
strhandle();
|
||||
break;
|
||||
default:
|
||||
fprintf( stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", (uchar)ascii,
|
||||
isprint( ascii ) ? ascii : '.' );
|
||||
terminalDiagnostic( "erresc: unknown sequence ESC 0x%02X '%c'\n", (uchar)ascii,
|
||||
isprint( ascii ) ? ascii : '.' );
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
@@ -2996,7 +3125,7 @@ void TerminalEmulator::tresize( int col, int row ) {
|
||||
bool is_alt = IS_SET( MODE_ALTSCREEN );
|
||||
|
||||
if ( col < 1 || row < 1 ) {
|
||||
fprintf( stderr, "tresize: error resizing to %dx%d\n", col, row );
|
||||
terminalDiagnostic( "tresize: error resizing to %dx%d\n", col, row );
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3205,9 +3334,10 @@ void TerminalEmulator::drawregion( ITerminalDisplay& dpy, int x1, int y1, int x2
|
||||
}
|
||||
|
||||
void TerminalEmulator::draw() {
|
||||
// If a synchronized update is in progress, skip the physical render
|
||||
// if ( mTerm.is_syncing )
|
||||
// return;
|
||||
// DEC private mode 2026 makes the bytes between DECSET and DECRST one presentation unit.
|
||||
// Parsing continues normally, but no partially cleared/rebuilt frame may reach the UI.
|
||||
if ( mTerm.is_syncing )
|
||||
return;
|
||||
|
||||
int cx = mTerm.c.x /*, ocx = term.ocx, ocy = term.ocy*/;
|
||||
|
||||
@@ -3284,28 +3414,47 @@ void TerminalEmulator::osc_color_response( int num, int index, int is_osc4 ) {
|
||||
unsigned char r, g, b;
|
||||
|
||||
if ( xgetcolor( is_osc4 ? num : index, &r, &g, &b ) ) {
|
||||
fprintf( stderr, "erresc: failed to fetch %s color %d\n", is_osc4 ? "osc4" : "osc",
|
||||
is_osc4 ? num : index );
|
||||
terminalDiagnostic( "erresc: failed to fetch %s color %d\n", is_osc4 ? "osc4" : "osc",
|
||||
is_osc4 ? num : index );
|
||||
return;
|
||||
}
|
||||
|
||||
n = snprintf( buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
|
||||
is_osc4 ? "4;" : "", num, r, r, g, g, b, b );
|
||||
if ( n < 0 || n >= (int)sizeof( buf ) ) {
|
||||
fprintf( stderr, "error: %s while printing %s response\n",
|
||||
n < 0 ? "snprintf failed" : "truncation occurred", is_osc4 ? "osc4" : "osc" );
|
||||
terminalDiagnostic( "error: %s while printing %s response\n",
|
||||
n < 0 ? "snprintf failed" : "truncation occurred",
|
||||
is_osc4 ? "osc4" : "osc" );
|
||||
} else {
|
||||
ttywrite( buf, n, 1 );
|
||||
}
|
||||
}
|
||||
|
||||
int TerminalEmulator::colorScheme() {
|
||||
unsigned char red, green, blue;
|
||||
if ( xgetcolor( 259, &red, &green, &blue ) )
|
||||
return 0;
|
||||
// The protocol describes the OS preference, which eterm does not store separately. The active
|
||||
// background is the useful equivalent for applications choosing contrasting colors.
|
||||
return red * 299 + green * 587 + blue * 114 < 128000 ? 1 : 2;
|
||||
}
|
||||
|
||||
void TerminalEmulator::reportColorScheme() {
|
||||
const int scheme = colorScheme();
|
||||
if ( !scheme )
|
||||
return;
|
||||
char buf[16];
|
||||
const int len = snprintf( buf, sizeof( buf ), "\033[?997;%dn", scheme );
|
||||
ttywrite( buf, len, 0 );
|
||||
}
|
||||
|
||||
void TerminalEmulator::mousereport( const TerminalMouseEventType& type, const Vector2i& pos,
|
||||
const Uint32& flags, const Uint32& mod ) {
|
||||
if ( !xgetmode( (TerminalWinMode)MODE_MOUSE ) && !xgetmode( MODE_MOUSESGR ) &&
|
||||
( TerminalMouseEventType::MouseButtonDown == type ||
|
||||
TerminalMouseEventType::MouseButtonRelease == type ) ) {
|
||||
/* If mouse mode is not enabled, we send arrow keys for scroll events */
|
||||
if ( type == TerminalMouseEventType::MouseButtonDown &&
|
||||
if ( type == TerminalMouseEventType::MouseButtonDown && xgetmode( MODE_ALTSCRROLL ) &&
|
||||
( flags & ( EE_BUTTON_WUMASK | EE_BUTTON_WDMASK ) ) && tisaltscr() ) {
|
||||
char buf[64];
|
||||
int len = 0;
|
||||
@@ -3568,7 +3717,6 @@ void TerminalEmulator::resize( int columns, int rows ) {
|
||||
return;
|
||||
}
|
||||
|
||||
mTerm.is_syncing = true;
|
||||
tresize( columns, rows );
|
||||
|
||||
redraw();
|
||||
@@ -3588,6 +3736,11 @@ bool TerminalEmulator::update() {
|
||||
_die( "Failed to resize pty!" );
|
||||
}
|
||||
|
||||
redraw();
|
||||
}
|
||||
|
||||
// A client that fails to close a synchronized update must not freeze presentation forever.
|
||||
if ( mTerm.is_syncing && mSynchronizedUpdateClock.getElapsedTime() >= Seconds( 1 ) ) {
|
||||
mTerm.is_syncing = false;
|
||||
redraw();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ class TerminalSession::WorkerDisplay final : public ITerminalDisplay {
|
||||
|
||||
void resetColors() {
|
||||
mPalette = mInitialPalette;
|
||||
if ( mEmulator )
|
||||
mEmulator->notifyColorSchemeChanged();
|
||||
Event event{ EventType::Color };
|
||||
event.value = -1;
|
||||
mSession.enqueueEvent( std::move( event ), false );
|
||||
@@ -144,6 +146,8 @@ class TerminalSession::WorkerDisplay final : public ITerminalDisplay {
|
||||
if ( !parsed )
|
||||
return 1;
|
||||
setPaletteColor( index, color );
|
||||
if ( mEmulator )
|
||||
mEmulator->notifyColorSchemeChanged();
|
||||
Event event{ EventType::Color };
|
||||
event.data = name ? name : "";
|
||||
event.value = static_cast<int>( index );
|
||||
@@ -195,6 +199,8 @@ class TerminalSession::WorkerDisplay final : public ITerminalDisplay {
|
||||
void setPalette( TerminalColorPalette palette ) {
|
||||
mInitialPalette = palette;
|
||||
mPalette = std::move( palette );
|
||||
if ( mEmulator )
|
||||
mEmulator->notifyColorSchemeChanged();
|
||||
}
|
||||
|
||||
void setFocused( bool focused ) {
|
||||
@@ -503,6 +509,7 @@ void TerminalSession::processCommand( Command&& command ) {
|
||||
mWorkerDisplay->setFocused( value.value );
|
||||
mEmulator->redraw();
|
||||
} else if constexpr ( std::is_same_v<T, CursorModeCommand> ) {
|
||||
mEmulator->setDefaultCursorMode( value.mode );
|
||||
mWorkerDisplay->setCursorMode( value.mode );
|
||||
mEmulator->redraw();
|
||||
} else if constexpr ( std::is_same_v<T, PaletteCommand> ) {
|
||||
@@ -554,4 +561,4 @@ void TerminalSession::processCommand( Command&& command ) {
|
||||
std::move( command ) );
|
||||
}
|
||||
|
||||
}} // namespace eterm::Terminal
|
||||
}} // namespace eterm::Terminal
|
||||
|
||||
@@ -339,18 +339,150 @@ UTEST( eterm_session, concurrent_shutdown_is_idempotent ) {
|
||||
class MockDisplay : public ITerminalDisplay {
|
||||
public:
|
||||
int mDrawLines{ 0 };
|
||||
int mDrawEnds{ 0 };
|
||||
uint32_t mFirstMode{ 0 };
|
||||
uint32_t mSecondMode{ 0 };
|
||||
TerminalGlyph mFirstGlyph;
|
||||
TerminalGlyph mSecondGlyph;
|
||||
std::vector<Uint32> mResetColorIndices;
|
||||
int mResetColorsCount{ 0 };
|
||||
Uint32 mBackground{ 0x101010FF };
|
||||
bool drawBegin( Uint32, Uint32 ) override { return true; }
|
||||
void drawLine( Line line, int, int, int ) override {
|
||||
void drawLine( Line line, int, int y, int ) override {
|
||||
++mDrawLines;
|
||||
mFirstMode = line[0].mode;
|
||||
mSecondMode = line[1].mode;
|
||||
if ( y == 0 ) {
|
||||
mFirstMode = line[0].mode;
|
||||
mSecondMode = line[1].mode;
|
||||
mFirstGlyph = line[0];
|
||||
mSecondGlyph = line[1];
|
||||
}
|
||||
}
|
||||
void drawCursor( int, int, TerminalGlyph, int, int, TerminalGlyph ) override {}
|
||||
void drawEnd() override {}
|
||||
void drawEnd() override { ++mDrawEnds; }
|
||||
void resetColors() override { ++mResetColorsCount; }
|
||||
int resetColor( const Uint32& index, const char* ) override {
|
||||
mResetColorIndices.emplace_back( index );
|
||||
return 0;
|
||||
}
|
||||
bool getColor( const Uint32& index, unsigned char* r, unsigned char* g,
|
||||
unsigned char* b ) override {
|
||||
if ( index == 259 ) {
|
||||
*r = static_cast<unsigned char>( mBackground >> 24 );
|
||||
*g = static_cast<unsigned char>( mBackground >> 16 );
|
||||
*b = static_cast<unsigned char>( mBackground >> 8 );
|
||||
return true;
|
||||
}
|
||||
if ( index > 1 )
|
||||
return false;
|
||||
*r = static_cast<unsigned char>( 1 + index * 3 );
|
||||
*g = static_cast<unsigned char>( 2 + index * 3 );
|
||||
*b = static_cast<unsigned char>( 3 + index * 3 );
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
UTEST( eterm, modern_csi_prefixes_do_not_claim_unsupported_keyboard_protocol ) {
|
||||
auto pty = std::make_unique<MockPty>();
|
||||
pty->mBuffer = "\033[?u\033[>7u\033[<1u\033[<u\033[=3u";
|
||||
pty->mLoopWrites = false;
|
||||
MockPty* ptyPtr = pty.get();
|
||||
auto process = std::make_unique<MockProcess>();
|
||||
auto display = std::make_shared<MockDisplay>();
|
||||
auto term = TerminalEmulator::create( std::move( pty ), std::move( process ), display, 100 );
|
||||
|
||||
term->update();
|
||||
|
||||
EXPECT_TRUE( ptyPtr->mWrites.empty() );
|
||||
}
|
||||
|
||||
UTEST( eterm, cursor_style_and_xterm_version_queries ) {
|
||||
auto pty = std::make_unique<MockPty>();
|
||||
pty->mBuffer = "\033[0 q\033[6 q\033[>0q";
|
||||
pty->mLoopWrites = false;
|
||||
MockPty* ptyPtr = pty.get();
|
||||
auto process = std::make_unique<MockProcess>();
|
||||
auto display = std::make_shared<MockDisplay>();
|
||||
auto term = TerminalEmulator::create( std::move( pty ), std::move( process ), display, 100 );
|
||||
|
||||
term->update();
|
||||
|
||||
EXPECT_EQ( TerminalCursorMode::SteadyBar, display->getCursorMode() );
|
||||
EXPECT_EQ( static_cast<size_t>( 0 ), ptyPtr->mWrites.find( "\033P>|eterm " ) );
|
||||
ASSERT_TRUE( ptyPtr->mWrites.size() >= 2 );
|
||||
EXPECT_STDSTREQ( "\033\\", ptyPtr->mWrites.substr( ptyPtr->mWrites.size() - 2 ) );
|
||||
}
|
||||
|
||||
UTEST( eterm, cursor_style_zero_uses_blinking_configured_shape ) {
|
||||
auto pty = std::make_unique<MockPty>();
|
||||
pty->mBuffer = "\033[2 q\033[0 q";
|
||||
auto process = std::make_unique<MockProcess>();
|
||||
auto display = std::make_shared<MockDisplay>();
|
||||
auto term = TerminalEmulator::create( std::move( pty ), std::move( process ), display, 100 );
|
||||
term->setDefaultCursorMode( TerminalCursorMode::SteadyUnderline );
|
||||
|
||||
term->update();
|
||||
|
||||
EXPECT_EQ( TerminalCursorMode::BlinkUnderline, display->getCursorMode() );
|
||||
}
|
||||
|
||||
UTEST( eterm, osc_hyperlink_markers_are_recognized ) {
|
||||
auto pty = std::make_unique<MockPty>();
|
||||
pty->mBuffer = "\033]8;id=codex;https://example.com/\033\\OK\033]8;;\033\\";
|
||||
pty->mLoopWrites = false;
|
||||
MockPty* ptyPtr = pty.get();
|
||||
auto process = std::make_unique<MockProcess>();
|
||||
auto display = std::make_shared<MockDisplay>();
|
||||
auto term = TerminalEmulator::create( std::move( pty ), std::move( process ), display, 100 );
|
||||
|
||||
term->update();
|
||||
|
||||
EXPECT_EQ( static_cast<Rune>( 'O' ), display->mFirstGlyph.u );
|
||||
EXPECT_EQ( static_cast<Rune>( 'K' ), display->mSecondGlyph.u );
|
||||
EXPECT_TRUE( ptyPtr->mWrites.empty() );
|
||||
}
|
||||
|
||||
UTEST( eterm, alternate_scroll_mode_controls_wheel_key_translation ) {
|
||||
auto pty = std::make_unique<MockPty>();
|
||||
pty->mBuffer = "\033[?1049h\033[?1007l";
|
||||
pty->mLoopWrites = false;
|
||||
MockPty* ptyPtr = pty.get();
|
||||
auto process = std::make_unique<MockProcess>();
|
||||
auto display = std::make_shared<MockDisplay>();
|
||||
auto term = TerminalEmulator::create( std::move( pty ), std::move( process ), display, 100 );
|
||||
|
||||
term->update();
|
||||
term->mousereport( TerminalMouseEventType::MouseButtonDown, { 0, 0 }, EE_BUTTON_WUMASK, 0 );
|
||||
EXPECT_TRUE( ptyPtr->mWrites.empty() );
|
||||
|
||||
ptyPtr->mBuffer += "\033[?1007h";
|
||||
term->update();
|
||||
term->mousereport( TerminalMouseEventType::MouseButtonDown, { 0, 0 }, EE_BUTTON_WUMASK, 0 );
|
||||
EXPECT_STDSTREQ( "\033[A", ptyPtr->mWrites );
|
||||
}
|
||||
|
||||
UTEST( eterm, color_scheme_query_and_change_notification ) {
|
||||
auto pty = std::make_unique<MockPty>();
|
||||
pty->mBuffer = "\033[?996n\033[?2031h";
|
||||
pty->mLoopWrites = false;
|
||||
MockPty* ptyPtr = pty.get();
|
||||
auto process = std::make_unique<MockProcess>();
|
||||
auto display = std::make_shared<MockDisplay>();
|
||||
auto term = TerminalEmulator::create( std::move( pty ), std::move( process ), display, 100 );
|
||||
|
||||
term->update();
|
||||
EXPECT_STDSTREQ( "\033[?997;1n", ptyPtr->mWrites );
|
||||
|
||||
display->mBackground = 0xF0F0F0FF;
|
||||
term->notifyColorSchemeChanged();
|
||||
EXPECT_STDSTREQ( "\033[?997;1n\033[?997;2n", ptyPtr->mWrites );
|
||||
|
||||
ptyPtr->mBuffer += "\033[?2031l";
|
||||
term->update();
|
||||
display->mBackground = 0x101010FF;
|
||||
term->notifyColorSchemeChanged();
|
||||
EXPECT_STDSTREQ( "\033[?997;1n\033[?997;2n", ptyPtr->mWrites );
|
||||
}
|
||||
|
||||
UTEST( eterm, basic_write ) {
|
||||
auto pty = std::make_unique<MockPty>();
|
||||
auto process = std::make_unique<MockProcess>();
|
||||
@@ -366,6 +498,93 @@ UTEST( eterm, basic_write ) {
|
||||
EXPECT_STDSTREQ( "ABC", term->getSelection() );
|
||||
}
|
||||
|
||||
UTEST( eterm, synchronized_updates_publish_only_complete_frames ) {
|
||||
auto pty = std::make_unique<MockPty>();
|
||||
auto process = std::make_unique<MockProcess>();
|
||||
auto display = std::make_shared<MockDisplay>();
|
||||
auto term = TerminalEmulator::create( std::move( pty ), std::move( process ), display, 100 );
|
||||
|
||||
term->update();
|
||||
display->mDrawEnds = 0;
|
||||
const char partialFrame[] = "\033[?2026h\033[2Jpartial";
|
||||
term->write( partialFrame, sizeof( partialFrame ) - 1 );
|
||||
term->update();
|
||||
|
||||
EXPECT_EQ( 0, display->mDrawEnds );
|
||||
|
||||
const char completeFrame[] = "\033[Hcomplete\033[?2026l";
|
||||
term->write( completeFrame, sizeof( completeFrame ) - 1 );
|
||||
term->update();
|
||||
|
||||
EXPECT_TRUE( display->mDrawEnds > 0 );
|
||||
term->selstart( 0, 0, 0 );
|
||||
term->selextend( 7, 0, SEL_REGULAR, false );
|
||||
EXPECT_STDSTREQ( "complete", term->getSelection() );
|
||||
}
|
||||
|
||||
UTEST( eterm, sgr_colon_subparameters_preserve_groups_and_optional_color_space ) {
|
||||
auto pty = std::make_unique<MockPty>();
|
||||
auto process = std::make_unique<MockProcess>();
|
||||
auto display = std::make_shared<MockDisplay>();
|
||||
auto term = TerminalEmulator::create( std::move( pty ), std::move( process ), display, 100 );
|
||||
|
||||
const char sequence[] = "\033[58:2::255:192:185;59;1;38:2::12:34:56;48:5:42mX";
|
||||
term->write( sequence, sizeof( sequence ) - 1 );
|
||||
term->update();
|
||||
|
||||
EXPECT_TRUE( display->mFirstGlyph.mode & ATTR_BOLD );
|
||||
EXPECT_EQ( 0x010C2238u, display->mFirstGlyph.fg );
|
||||
EXPECT_EQ( 42u, display->mFirstGlyph.bg );
|
||||
}
|
||||
|
||||
UTEST( eterm, invalid_indexed_color_keeps_the_previous_color ) {
|
||||
auto pty = std::make_unique<MockPty>();
|
||||
auto process = std::make_unique<MockProcess>();
|
||||
auto display = std::make_shared<MockDisplay>();
|
||||
auto term = TerminalEmulator::create( std::move( pty ), std::move( process ), display, 100 );
|
||||
|
||||
const char sequence[] = "\033[31mA\033[38;5;283mB";
|
||||
term->write( sequence, sizeof( sequence ) - 1 );
|
||||
term->update();
|
||||
|
||||
EXPECT_EQ( 1u, display->mFirstGlyph.fg );
|
||||
EXPECT_EQ( 1u, display->mSecondGlyph.fg );
|
||||
}
|
||||
|
||||
UTEST( eterm, osc_palette_queries_return_each_requested_color ) {
|
||||
auto pty = std::make_unique<MockPty>();
|
||||
pty->mBuffer = "\033]4;0;?;1;?\a";
|
||||
pty->mLoopWrites = false;
|
||||
MockPty* ptyPtr = pty.get();
|
||||
auto process = std::make_unique<MockProcess>();
|
||||
auto display = std::make_shared<MockDisplay>();
|
||||
auto term = TerminalEmulator::create( std::move( pty ), std::move( process ), display, 100 );
|
||||
|
||||
term->update();
|
||||
|
||||
EXPECT_STDSTREQ( "\033]4;0;rgb:0101/0202/0303\a\033]4;1;rgb:0404/0505/0606\a",
|
||||
ptyPtr->mWrites );
|
||||
}
|
||||
|
||||
UTEST( eterm, osc_color_resets_reach_palette_and_dynamic_defaults ) {
|
||||
auto pty = std::make_unique<MockPty>();
|
||||
pty->mBuffer = "\033]104;1;2\a\033]110\a\033]111\a\033]112\a\033]104\a";
|
||||
pty->mLoopWrites = false;
|
||||
auto process = std::make_unique<MockProcess>();
|
||||
auto display = std::make_shared<MockDisplay>();
|
||||
auto term = TerminalEmulator::create( std::move( pty ), std::move( process ), display, 100 );
|
||||
|
||||
term->update();
|
||||
|
||||
ASSERT_EQ( static_cast<size_t>( 5 ), display->mResetColorIndices.size() );
|
||||
EXPECT_EQ( 1u, display->mResetColorIndices[0] );
|
||||
EXPECT_EQ( 2u, display->mResetColorIndices[1] );
|
||||
EXPECT_EQ( 258u, display->mResetColorIndices[2] );
|
||||
EXPECT_EQ( 259u, display->mResetColorIndices[3] );
|
||||
EXPECT_EQ( 256u, display->mResetColorIndices[4] );
|
||||
EXPECT_EQ( 2, display->mResetColorsCount );
|
||||
}
|
||||
|
||||
UTEST( eterm, selection_redraw_while_idle ) {
|
||||
auto pty = std::make_unique<MockPty>();
|
||||
auto process = std::make_unique<MockProcess>();
|
||||
|
||||
Reference in New Issue
Block a user