Fix TUI issues with Alt-Gr on Windows (#6799)

This PR fixes keyboard handling for the Right Alt (aka "Alt-Gr") key on
Windows. This key appears on keyboards in Central and Eastern Europe.
Codex has effectively never worked for Windows users in these regions
because the code didn't properly handle this key, which is used for
typing common symbols like `\` and `@`.

A few days ago, I merged a [community-authored
PR](https://github.com/openai/codex/pull/6720) that supplied a partial
fix for this issue. Upon closer inspect, that PR was 1) too broad (not
scoped to Windows only) and 2) incomplete (didn't fix all relevant code
paths, so paste was still broken).

This improvement is based on another [community-provided
PR](https://github.com/openai/codex/pull/3241) by @marektomas-cz. He
submitted it back in September and later closed it because it didn't
receive any attention.

This fix addresses the following bugs: #5922, #3046, #3092, #3519,
#5684, #5843.
This commit is contained in:
Eric Traut
2025-11-17 17:18:16 -06:00
committed by GitHub
parent ab2e7499f8
commit 8bebe86a47
3 changed files with 24 additions and 10 deletions

View File

@@ -1,3 +1,4 @@
use crate::key_hint::has_ctrl_or_alt;
use crossterm::event::KeyCode;
use crossterm::event::KeyEvent;
use crossterm::event::KeyEventKind;
@@ -1103,8 +1104,7 @@ impl ChatComposer {
..
} = input
{
let has_ctrl_or_alt =
modifiers.contains(KeyModifiers::CONTROL) || modifiers.contains(KeyModifiers::ALT);
let has_ctrl_or_alt = has_ctrl_or_alt(modifiers);
if !has_ctrl_or_alt {
// Non-ASCII characters (e.g., from IMEs) can arrive in quick bursts and be
// misclassified by paste heuristics. Flush any active burst buffer and insert
@@ -1174,8 +1174,7 @@ impl ChatComposer {
} = input;
match code {
KeyCode::Char(_) => {
let has_ctrl_or_alt = modifiers.contains(KeyModifiers::CONTROL)
|| modifiers.contains(KeyModifiers::ALT);
let has_ctrl_or_alt = has_ctrl_or_alt(modifiers);
if has_ctrl_or_alt {
self.paste_burst.clear_window_after_non_char();
}

View File

@@ -1,3 +1,4 @@
use crate::key_hint::is_altgr;
use crossterm::event::KeyCode;
use crossterm::event::KeyEvent;
use crossterm::event::KeyModifiers;
@@ -247,16 +248,13 @@ impl TextArea {
} if modifiers == (KeyModifiers::CONTROL | KeyModifiers::ALT) => {
self.delete_backward_word()
},
// Windows AltGr generates ALT|CONTROL; treat as a plain character input unless
// we match a specific Control+Alt binding above.
KeyEvent {
code: KeyCode::Char(c),
modifiers,
..
} if modifiers.contains(KeyModifiers::ALT)
&& modifiers.contains(KeyModifiers::CONTROL) =>
{
// AltGr on many keyboards reports as Ctrl+Alt; treat it as a literal char.
self.insert_str(&c.to_string());
},
} if is_altgr(modifiers) => self.insert_str(&c.to_string()),
KeyEvent {
code: KeyCode::Backspace,
modifiers: KeyModifiers::ALT,
@@ -1464,6 +1462,7 @@ mod tests {
assert_eq!(t.cursor(), 3);
}
#[cfg_attr(not(windows), ignore = "AltGr modifier only applies on Windows")]
#[test]
fn altgr_ctrl_alt_char_inserts_literal() {
let mut t = ta_with("");

View File

@@ -89,3 +89,19 @@ impl From<&KeyBinding> for Span<'static> {
fn key_hint_style() -> Style {
Style::default().dim()
}
pub(crate) fn has_ctrl_or_alt(mods: KeyModifiers) -> bool {
(mods.contains(KeyModifiers::CONTROL) || mods.contains(KeyModifiers::ALT)) && !is_altgr(mods)
}
#[cfg(windows)]
#[inline]
pub(crate) fn is_altgr(mods: KeyModifiers) -> bool {
mods.contains(KeyModifiers::ALT) && mods.contains(KeyModifiers::CONTROL)
}
#[cfg(not(windows))]
#[inline]
pub(crate) fn is_altgr(_mods: KeyModifiers) -> bool {
false
}