feat(acceptor): expose the client's keyboard layout on AcceptorResult#1397
Conversation
The client announces its keyboard layout identifier (KLID) in the GCC Client Core Data (MS-RDPBCGR 2.2.1.3.2, `keyboardLayout`). The acceptor already decodes that block but keeps only the early-capability flags, so a server built on ironrdp-acceptor has no way to learn the client's layout. Surface it on `AcceptorResult::keyboard_layout` (0 when the client did not announce one), mirroring the existing `credentials` (Devolutions#1155) and `message_channel_id` (Devolutions#1347) additions. It is captured in BasicSettingsWaitInitial and stored on `Acceptor`, so it survives a deactivation-reactivation like `desktop_size`. A server can use it to select a matching server-side keyboard layout without touching any local input state — e.g. translating positional scancodes through the announced layout for non-US clients.
|
Flagging this small one for your queue 🙂 — CI green and Only open point is testing: the acceptor lib is |
Benoît Cortier (CBenoit)
left a comment
There was a problem hiding this comment.
LGTM! Simple enough 🙂
There was a problem hiding this comment.
Pull request overview
This PR exposes the client-announced keyboard layout identifier (KLID) from the GCC Client Core Data on ironrdp-acceptor’s AcceptorResult, so server implementations can select a matching server-side keyboard layout (and keep the value across deactivation/reactivation).
Changes:
- Add
keyboard_layout: u32storage onAcceptorand propagate it through deactivation/reactivation. - Expose
keyboard_layout: u32onAcceptorResult. - Capture the value during
BasicSettingsWaitInitialfromgcc_blocks.core.keyboard_layout.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -54,6 +55,14 @@ pub struct AcceptorResult { | |||
| /// channel. `None` when the client did not request it. | |||
| /// This is the low word of a Windows locale identifier (e.g. `0x0000_0409` | ||
| /// for US English, `0x0000_040C` for French). `0` when the client did not | ||
| /// announce one. Servers can use it to pick a server-side keyboard layout | ||
| /// matching the client without changing any local input state. |
| let gcc_blocks = settings_initial.conference_create_request.into_gcc_blocks(); | ||
| let early_capability = gcc_blocks.core.optional_data.early_capability_flags; | ||
| let client_wants_message_channel = gcc_blocks.message_channel.is_some(); | ||
| self.keyboard_layout = gcc_blocks.core.keyboard_layout; | ||
|
|
What
The client announces its keyboard layout identifier (KLID) in the GCC Client Core Data (MS-RDPBCGR 2.2.1.3.2,
keyboardLayout).ironrdp-acceptoralready decodes that block inBasicSettingsWaitInitialbut keeps only the early-capability flags, so a server built on the acceptor has no way to learn the client's layout.This adds
AcceptorResult::keyboard_layout: u32(the announced KLID;0when the client didn't send one), captured duringBasicSettingsWaitInitialand stored onAcceptorso it survives a deactivation-reactivation (same asdesktop_size).Why
A server can use it to select a matching server-side keyboard layout without touching any local input state — e.g. translating positional scancodes through the client's announced layout for non-US clients. Today the KLID is decoded and then thrown away.
This mirrors the existing pattern of surfacing already-parsed client data on
AcceptorResult:credentialsmessage_channel_idThe motivating downstream is a macOS RDP server (macrdp) that auto-detects the client's keyboard layout; this is the last piece it vendors a fork of the acceptor for on this front.
Notes
get_resultconstructs internally — labeledfeat(acceptor):to match feat(acceptor): expose received client credentials in AcceptorResult #1155/feat(acceptor): negotiate the MCS message channel #1347 (same shape).test = false(Cargo.toml) and there's no acceptor harness inironrdp-testsuite-core, so there's no in-tree place to assert it (the value is a straight passthrough of the already-decodedgcc_blocks.core.keyboard_layout). Happy to add coverage wherever you'd prefer it to live.cargo build/cargo fmt --check/cargo clippy -p ironrdp-acceptor --all-targets -- -D warningsclean (only the pre-existing workspace MSRV-config notice).