Repository navigation
AltGr (ISO_Level3_Shift) allocated to a dynamic spare keycode instead of the real layout keycode — breaks nested-RDP AltGr handling #370
Description
Environment
- KasmVNC 1.4.1 (shipped via kasmweb/core-kasmos:1.18.0-rolling-daily)
- KDE Plasma desktop, container XKB layout explicitly set to "de" (setxkbmap de + kxkbrc)
- Browser client: Chrome, connecting to a physical German keyboard
- Downstream: an RDP client inside the session (Devolutions Remote Desktop
Manager, bundled FreeRDP 3.16.0) is what surfaces the practical impact,
but the root cause reproduces with plain xev, no RDP involved.
Problem
With keyboard.raw_keyboard: false (default), pressing the physical AltGr
key produces an X11 KeyPress event with keysym 0xfe03 (ISO_Level3_Shift)
on keycode 92 — a "spare"/dynamically-allocated keycode — instead of
keycode 108, the standard evdev position for the physical Right-Alt/
AltGr key that a "de" XKB layout assigns it to.
xev output:
KeyPress event, ...
state 0x0, keycode 92 (keysym 0xfe03, ISO_Level3_Shift), ...
Any downstream consumer that relies on a fixed/expected keycode for AltGr
(e.g. FreeRDP's internal X11-keycode-to-VK table) never recognizes this as
a modifier and falls back to broken behavior (in FreeRDP 3.x's case,
transmitting the composed character as Unicode input instead of a proper
AltGr-modified keypress, since it gets VK_NONE for keycode 92).
Root cause (as far as I can tell from the source)
In unix/xserver/hw/vnc/XserverDesktop.cc:
void XserverDesktop::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down)
{
if (!rawKeyboard)
keycode = 0;
vncKeyboardEvent(keysym, keycode, down);
}
When raw_keyboard is off, the keycode from the client is discarded
entirely and re-derived purely from the keysym elsewhere, presumably by
allocating it onto the next free/unused keycode — which is why it lands on
92 instead of reusing the layout's real 108, regardless of what the
container's actual XKB keymap (via setxkbmap/xmodmap) defines.
We tested three independent workarounds, all unsuccessful, which suggests
this dynamic allocation isn't visible to other X11 clients through normal
means (XChangeKeyboardMapping/MappingNotify):
- Forcing keycode 108 = ISO_Level3_Shift via xmodmap at session start —
zero effect on the keycode delivered in the resulting KeyPress event. - Setting
keyboard.raw_keyboard: true— preserves the real keycode
(108, confirmed via xev), but breaks AltGr modifier-hold semantics
entirely: AltGr+ no longer composes the Level3 character at all,
both locally in-session and over RDP. This matches a known, unresolved
TigerVNC issue ([BUG] AltGr still not working TigerVNC/tigervnc#1715, closed as "notourbug"), so we
suspect KasmVNC inherited the same limitation in -RawKeyboard mode. - "Priming" the keycode allocation by pressing AltGr once locally before
ever opening the downstream RDP client (to rule out a stale
keymap-snapshot-at-connect-time theory on the FreeRDP side) — also had
no effect.
We also found #357, which describes a different symptom
(Ctrl+Shift+Minus sending the wrong sequence) in the same general area —
introduced by the 1.4.0 "Fixed bug with Ctrl key not working on foreign
language keyboards with Chromium based browsers" change. Given the
overlap, we suspect this whole area (fake-modifier/dynamic-keycode
handling for foreign layouts) may share a common cause.
Ask
Is there a way to make the dynamic keysym allocation check the layout's
existing/expected keycode for well-known modifier keysyms like
ISO_Level3_Shift before allocating a spare one, and/or announce it to
other X11 clients through the standard keymap-change mechanism? Or is
raw_keyboard meant to be the fix here, in which case the AltGr-composition
break in that mode would need to be looked at separately?
Happy to provide a full container reproduction / more xev traces on
request.
Activity
N3m3515 commentedon Sep 1, 2026
Follow-up with a more precise finding, and a correction to something I
speculated about earlier in the report.
I initially assumed
raw_keyboard: truedeterministically breaks AltGr'smodifier-hold semantics. Comparing three xev traces of the exact same
action (physically hold AltGr, press ß) shows that's not quite right -
it's a race condition, not a deterministic break, and it reproduces in
BOTH keyboard.raw_keyboard settings.
The traces
Native X11 (no KasmVNC involved, control test on a separate machine):
A single, clean press/hold/release cycle - no repeats:
KasmVNC, raw_keyboard: false (default): AltGr fires roughly 8 rapid
KeyPress/KeyRelease cycles on its own keycode before the ß event ever
arrives - even though the physical key was only held once, not repeatedly
pressed. In the trace I captured, the ß KeyPress happened to land while
one of those cycles was still in its "pressed" phase, so it composed
correctly:
KasmVNC, raw_keyboard: true: Same flutter, but worse - roughly 19
cycles this time, now on keycode 108 (confirming raw_keyboard does fix
the keycode value itself). This time the last release lands 1ms before
the ß press, so the modifier is no longer considered held when ß arrives,
producing plain "ß" instead of backslash:
Revised understanding
The keycode-92-vs-108 mismatch (described in the original report) is
still the deterministic reason FreeRDP/RDM specifically always fails,
since it depends on a fixed keycode number regardless of timing.
But the local composition failure I attributed to raw_keyboard mode
breaking modifier semantics looks instead like a symptom of this same
underlying flutter - a genuine race condition, not a clean behavioral
difference tied to raw_keyboard. raw_keyboard doesn't introduce the
flutter (it's present in the default path too, just with fewer cycles:
8 vs 19 in my traces), it just seems to make the race more likely to
land the wrong way, and happens to also fix the keycode number, which
made the composition failure more visible/reproducible for us.
The open question this raises: why does AltGr fire repeated
press/release cycles at all while being physically held once? A
modifier key auto-repeating like a regular character key looks like the
actual root anomaly here, upstream of both the keycode-92 assignment and
the raw_keyboard behavior - possibly worth looking at independently of
the rest of this report.
Happy to provide the full xev logs for all three traces if useful.