Skip to content

Commit

Permalink
Merge pull request #1261 from contour-terminal/feature/vt-keyboard-CSIu
Browse files Browse the repository at this point in the history
Add Keyboard protocol extension `CSI u`
  • Loading branch information
christianparpart committed Oct 14, 2023
2 parents 58ce25e + 8a4b845 commit d12d057
Show file tree
Hide file tree
Showing 16 changed files with 708 additions and 90 deletions.
125 changes: 125 additions & 0 deletions docs/vt-extensions/csi-u-extended-keyboard-protocol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# CSIu Extended Keyboard Protocol

## TL;DR

The main problem with the classic way of processing keyboard input is, that it is highly ambiuous
when modifiers need to be used.

CSIu aims to solve this by enabling disambiguation of keyboard events that would be ambiguous otherwise.

## New VT Sequences

VT sequence | Short description
---------------------------|--------------------------
`CSI > {flags} u` | Enter extended keyboard protocol mode
`CSI = {flags} u` | Request enhancement to the currently active protocol
`CSI = {flags} ; {mode} u` | Request enhancement to the currently active protocol
`CSI < {count} u` | Leave the current keyboard protocol mode
`CSI < u` | Leave the current keyboard protocol mode

## How entering and leaving works

In order to more conveniently enable the application to configure the keyboard without
breaking the calling application, when the current program is about to exit, the terminal
leverages an internal stack of keyboard protocol flags.

When an application starts, it can push a new desired protocol state onto the keyboard protocol stack,
and thus get keyboard events respectively to the flags being set.

When the application exits, the flags are being previously stored on the top of the stack,
are now simply being popped from the flag stack, thus, effectively making the old state active again.

This stack must store at most 32 flags. The bottom-most stack has no flags set, and thus, will
act like the legacy keyboard protocol. This bottom-most stack item cannot be popped from the stack.

When the terminal hard-resets, the keyboard's flag stack is being set back to 1 element (the legacy flags).

## Operating Mode Flags

This is the number of flags that can be **or**'d together into a flag bitset either when entering
a new keyboard-protocol session, or when enhancing the currently active keyboard protocol session.

Binary | Decimal | short description
------------|---------|------------
`0b0'0001` | ` 1` | Disambiguation
`0b0'0010` | ` 2` | Report all event types (press, repeat, release)
`0b0'0100` | ` 4` | Report alternate keys
`0b0'1000` | ` 8` | Report all keys as control sequence
`0b1'0000` | `16` | Report associated text

## Entering CSIu mode

Syntax: `CSI > {flags} u`

By default, `flags` is set to `1`, but `flags` ben a a binary-or'd set of flags to enable.

Passing `0` will act like `1`.

### Flag: Disambiguation

Any non-ambiuous key event, such as regular text without any modifiers pressed, is is sent out in UTF-8 text as is.

Non-printable key events (such as `F3` or `ESC`), and printable text key events with modifiers being pressed,
are sent out in form of a control sequence.

Syntax (for some key events): `CSI ? {Code} ; {Modifier} ~`

Syntax (for text and most other key events): `CSI ? {Code} ; {Modifier} u`

`{Code}` is the numerical form of the unicode codepoint for textual key events,
and a special assigned numeric value from the Unicode PUA range otherwise.

Key | Code | Final Char
----------|-------------------
`<Enter>` | `27` | `u`

### Flag: Report all event types

With this flag enabled, an additional sub parameter to the modifier is passed
to indicate if the key was just pressed, repeated, or released.
A repeat-event is usually triggered by the operating system to simulate key press events
at a fixed rate without needing to press and release that key many times.

Event type | Code
------------|-----------
Key Press | `1`
Key Repeat | `2`
Key Release | `3`

### Report alternate keys

TODO ...

### Report all keys as control sequence

With this flag enabled, not just otherwise ambiguous events are sent as control sequence,
but every key event is sent as control sequence instead.

### Report associated text

TODO ...

## Change currently active protocol

Syntax: `CSI = {flags} ; {mode} u`

The top of the keyboard's flags stack can be changed in one of the three ways:

Mode value | description
-----------|------------------------------------------------
`1` | set given flags to currently active flags
`2` | add given flags to currently active flags
`3` | remove given flags from currently active flags

This will immediately take affect.

## Leaving CSIu mode

Syntax: `CSI < {count} u`

When terminating an application session, an application must pop off what was previously pushed
onto the keyboard's flags stack.

`count` can be greater than `1` in order to pop multiple flags at once.
Passing a bigger number than the number of elements in the flags stack minus the bottom-most legacy entry,
will truncate the number `count` value to number of elements in the flags stack minus `1`.
2 changes: 2 additions & 0 deletions metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@
<li>Adds VT sequence DECSSCLS (change scroll speed) and properly handle DECSCLM (enable slow scrolling mode) (#1204)</li>
<li>Adds VT sequence parameter ?996 to DSR to request a report of current color scheme dark/light mode hint.</li>
<li>Adds VT sequence `SM ?2031` and `RM ?2031` to enable/disable unsolicited DSR for color scheme updates by the user or OS.</li>
<li>Adds support the extended `CSIu` keyboard protocol to better report key modifiers.</li>
<li>Adds extended keyboard protocol support (CSI u) to better report key modifiers.</li>
<li>Adds support vor horizontal mouse scrolling event reporting sequences.</li>
<li>Adds percentage value to Indicator Statusline to indicate scroll offset in scrollback buffer.</li>
<li>Adds inheritance of profiles in configuration file based on default profile (#1063).</li>
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ nav:
- vt-extensions/clickable-links.md
- vt-extensions/vertical-line-marks.md
- vt-extensions/color-palette-update-notifications.md
# TODO(publish when ready): - vt-extensions/csi-u-extended-keyboard-protocol.md
- vt-extensions/synchronized-output.md
- vt-extensions/buffer-capture.md
- vt-extensions/font-settings.md
Expand Down
8 changes: 3 additions & 5 deletions src/contour/TerminalSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -694,10 +694,8 @@ void TerminalSession::sendKeyEvent(Key key, Modifier modifier, KeyboardEventType
terminal().sendKeyEvent(key, modifier, eventType, now);
}

void TerminalSession::sendCharEvent(char32_t value,
Modifier modifier,
KeyboardEventType eventType,
Timestamp now)
void TerminalSession::sendCharEvent(
char32_t value, uint32_t physicalKey, Modifier modifier, KeyboardEventType eventType, Timestamp now)
{
inputLog()("Character {} event received: {} {}",
eventType,
Expand All @@ -724,7 +722,7 @@ void TerminalSession::sendCharEvent(char32_t value,
return;
}
}
terminal().sendCharEvent(value, modifier, eventType, now);
terminal().sendCharEvent(value, physicalKey, modifier, eventType, now);
}

void TerminalSession::sendMousePressEvent(Modifier modifier,
Expand Down
1 change: 1 addition & 0 deletions src/contour/TerminalSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ class TerminalSession: public QAbstractItemModel, public vtbackend::Terminal::Ev
vtbackend::KeyboardEventType eventType,
Timestamp now);
void sendCharEvent(char32_t value,
uint32_t physicalKey,
vtbackend::Modifier modifier,
vtbackend::KeyboardEventType eventType,
Timestamp now);
Expand Down
66 changes: 44 additions & 22 deletions src/contour/helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ bool sendKeyEvent(QKeyEvent* event, vtbackend::KeyboardEventType eventType, Term

static auto constexpr KeyMappings = array {
// {{{
pair { Qt::Key_Escape, Key::Escape },
pair { Qt::Key_Enter, Key::Enter },
pair { Qt::Key_Return, Key::Enter },
pair { Qt::Key_Tab, Key::Tab },
pair { Qt::Key_Backspace, Key::Backspace },

pair { Qt::Key_Insert, Key::Insert },
pair { Qt::Key_Delete, Key::Delete },
pair { Qt::Key_Right, Key::RightArrow },
Expand Down Expand Up @@ -176,6 +182,21 @@ bool sendKeyEvent(QKeyEvent* event, vtbackend::KeyboardEventType eventType, Term
pair { Qt::Key_F18, Key::F18 },
pair { Qt::Key_F19, Key::F19 },
pair { Qt::Key_F20, Key::F20 },
pair { Qt::Key_F21, Key::F21 },
pair { Qt::Key_F22, Key::F22 },
pair { Qt::Key_F23, Key::F23 },
pair { Qt::Key_F24, Key::F24 },
pair { Qt::Key_F25, Key::F25 },
pair { Qt::Key_F26, Key::F26 },
pair { Qt::Key_F27, Key::F27 },
pair { Qt::Key_F28, Key::F28 },
pair { Qt::Key_F29, Key::F29 },
pair { Qt::Key_F30, Key::F30 },
pair { Qt::Key_F31, Key::F31 },
pair { Qt::Key_F32, Key::F32 },
pair { Qt::Key_F33, Key::F33 },
pair { Qt::Key_F34, Key::F34 },
pair { Qt::Key_F35, Key::F35 },

pair { Qt::Key_MediaPlay, Key::MediaPlay },
pair { Qt::Key_MediaStop, Key::MediaStop },
Expand All @@ -188,33 +209,30 @@ bool sendKeyEvent(QKeyEvent* event, vtbackend::KeyboardEventType eventType, Term
pair { Qt::Key_VolumeDown, Key::VolumeDown },
pair { Qt::Key_VolumeMute, Key::VolumeMute },

pair { Qt::Key_Control, Key::Control },
pair { Qt::Key_Alt, Key::Alt },
pair { Qt::Key_Control, Key::LeftControl }, // NB: Qt cannot distinguish between left and right
pair { Qt::Key_Alt, Key::LeftAlt }, // NB: Qt cannot distinguish between left and right
pair { Qt::Key_Meta, Key::LeftMeta }, // NB: Qt cannot distinguish between left and right
pair { Qt::Key_Super_L, Key::LeftSuper },
pair { Qt::Key_Super_R, Key::RightSuper },
pair { Qt::Key_Hyper_L, Key::LeftHyper },
pair { Qt::Key_Hyper_R, Key::RightHyper },
pair { Qt::Key_Meta, Key::Meta },

pair { Qt::Key_CapsLock, Key::CapsLock },
pair { Qt::Key_ScrollLock, Key::ScrollLock },
pair { Qt::Key_NumLock, Key::NumLock },
pair { Qt::Key_Print, Key::PrintScreen },
pair { Qt::Key_Pause, Key::Pause },
pair { Qt::Key_Menu, Key::Menu },

// pair { Qt::Key_Num

}; // }}}

static auto constexpr CharMappings = array {
// {{{
pair { Qt::Key_Return, '\r' }, pair { Qt::Key_AsciiCircum, '^' },
pair { Qt::Key_AsciiTilde, '~' }, pair { Qt::Key_Backslash, '\\' },
pair { Qt::Key_Bar, '|' }, pair { Qt::Key_BraceLeft, '{' },
pair { Qt::Key_BraceRight, '}' }, pair { Qt::Key_BracketLeft, '[' },
pair { Qt::Key_BracketRight, ']' }, pair { Qt::Key_QuoteLeft, '`' },
pair { Qt::Key_Underscore, '_' },
// pair { Qt::Key_Return, '\r' },
pair { Qt::Key_AsciiCircum, '^' }, pair { Qt::Key_AsciiTilde, '~' },
pair { Qt::Key_Backslash, '\\' }, pair { Qt::Key_Bar, '|' },
pair { Qt::Key_BraceLeft, '{' }, pair { Qt::Key_BraceRight, '}' },
pair { Qt::Key_BracketLeft, '[' }, pair { Qt::Key_BracketRight, ']' },
pair { Qt::Key_QuoteLeft, '`' }, pair { Qt::Key_Underscore, '_' },
}; // }}}

auto const modifiers = makeModifier(event->modifiers());
Expand All @@ -229,43 +247,47 @@ bool sendKeyEvent(QKeyEvent* event, vtbackend::KeyboardEventType eventType, Term
return true;
}

auto const physicalKey = event->nativeVirtualKey();
// NOLINTNEXTLINE(readability-qualified-auto)
if (auto const i = find_if(begin(CharMappings),
end(CharMappings),
[event](auto const& x) { return x.first == event->key(); });
i != end(CharMappings))
{
session.sendCharEvent(static_cast<char32_t>(i->second), modifiers, eventType, now);
session.sendCharEvent(static_cast<char32_t>(i->second), physicalKey, modifiers, eventType, now);
return true;
}

if (key == Qt::Key_Backtab)
{
session.sendCharEvent(U'\t', modifiers.with(Modifier::Shift), eventType, now);
session.sendCharEvent(U'\t', physicalKey, modifiers.with(Modifier::Shift), eventType, now);
return true;
}

#if defined(__apple__)
if (0x20 <= key && key < 0x80 && (modifiers.alt() && session.profile().optionKeyAsAlt))
{
auto const ch = static_cast<char32_t>(modifiers.shift() ? std::toupper(key) : std::tolower(key));
session.sendCharEvent(ch, modifiers, eventType, now);
session.sendCharEvent(ch, physicalKey, modifiers, eventType, now);
return true;
}
#endif

if (0x20 <= key && key < 0x80 && (modifiers.control()))
{
session.sendCharEvent(static_cast<char32_t>(key), modifiers, eventType, now);
session.sendCharEvent(static_cast<char32_t>(key), physicalKey, modifiers, eventType, now);
return true;
}

switch (key)
{
case Qt::Key_BraceLeft: session.sendCharEvent(L'[', modifiers, eventType, now); return true;
case Qt::Key_Equal: session.sendCharEvent(L'=', modifiers, eventType, now); return true;
case Qt::Key_BraceRight: session.sendCharEvent(L']', modifiers, eventType, now); return true;
case Qt::Key_Backspace: session.sendCharEvent(0x08, modifiers, eventType, now); return true;
// clang-format off
case Qt::Key_BraceLeft: session.sendCharEvent(L'[', physicalKey, modifiers, eventType, now); return true;
case Qt::Key_Equal: session.sendCharEvent(L'=', physicalKey, modifiers, eventType, now); return true;
case Qt::Key_BraceRight: session.sendCharEvent(L']', physicalKey, modifiers, eventType, now); return true;
case Qt::Key_Backspace: session.sendCharEvent(0x08, physicalKey, modifiers, eventType, now); return true;
default: break;
// clang-format on
}

if (!event->text().isEmpty())
Expand All @@ -276,10 +298,10 @@ bool sendKeyEvent(QKeyEvent* event, vtbackend::KeyboardEventType eventType, Term
// On OS/X the Alt-modifier does not seem to be passed to the terminal apps
// but rather remapped to whatever OS/X is mapping them to.
for (char32_t const ch: codepoints)
session.sendCharEvent(ch, modifiers.without(Modifier::Alt), eventType, now);
session.sendCharEvent(ch, physicalKey, modifiers.without(Modifier::Alt), eventType, now);
#else
for (char32_t const ch: codepoints)
session.sendCharEvent(ch, modifiers, eventType, now);
session.sendCharEvent(ch, physicalKey, modifiers, eventType, now);
#endif

return true;
Expand Down
2 changes: 2 additions & 0 deletions src/contour/helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ constexpr inline vtbackend::Modifier makeModifier(Qt::KeyboardModifiers qtModifi
if (qtModifiers & Qt::MetaModifier)
modifiers |= Modifier::Meta;
#endif
if (qtModifiers & Qt::KeypadModifier)
modifiers |= Modifier::NumLock;

return modifiers;
}
Expand Down
Loading

0 comments on commit d12d057

Please sign in to comment.