protonscr

Steam Input sends incorrect characters when using a non-QWERTY keyboard layout on Linux

steamopen Distro Family: UbuntuGeneral controller / Steam Input
ValveSoftware/steam-for-linux#13220 · opened 2026-05-18 by lhervier · updated 2026-06-29 · 3 comments · github
Llhervier 2026-05-18 github

Your system information

  • Steam client version (build number or date): 1778803745
  • Distribution (e.g. Ubuntu): Ubuntu 26.04 French
  • Opted into Steam client beta?: Yes
  • Have you checked for system updates?: Yes
  • Steam Logs: steam-logs.tar.gz
  • GPU: Nvidia/Intel/AMD AMD RX580

Please describe your issue in as much detail as possible:

I use Steam Input to bind keyboard keys to controller buttons. My OS is set to French, and my physical keyboard is an AZERTY keyboard. Steam Input's binding UI is QWERTY-based, which is expected — the intended behavior is that it maps key positions, not key labels. For example, binding the controller's X button to the QWERTY Q key should send A on an AZERTY layout, since A occupies that physical position. This works correctly on Windows.

On Linux, however, the behavior is inconsistent. Some bindings respect the AZERTY layout correctly, others do not, and some QWERTY keys are incorrectly mapped to the same output character, making certain AZERTY characters impossible to produce.

Observed behavior (QWERTY binding → actual output on Linux vs. expected output):

QWERTY binding Expected (AZERTY) / Observed on Windows Actual (Linux) Status
1 & & ✅ Correct
5 ( ( ✅ Correct
7 è è ✅ Correct
8 _ _ ✅ Correct
Q a q ❌ Wrong
M , m ❌ Wrong
. : ; ❌ Wrong
; m ; ❌ Collision with .
[ ^ ( ❌ Collision with 5
` ² è ❌ Collision with 7
\ $ _ ❌ Collision with 8

Several QWERTY keys produce the same output, and as a result some AZERTY characters (e.g. :) become impossible to type through Steam Input.

Workaround: Switching the OS keyboard layout to QWERTY resolves the issue, but is not a viable solution for French users.

This bug was first encountered when migrating from Windows 10.

Steps for reproducing this issue:

  1. Install a fresh Ubuntu 26.04 instance
  2. Set the system language and keyboard layout to French (AZERTY)
  3. Install the Steam client and opt into the beta
  4. Connect a controller
  5. Open Steam Settings → Controller → Advanced parameters → Desktop Configuration
  6. Bind the A button to the . key (QWERTY dot) in Steam Input
  7. Bind the B button to the ; key (QWERTY semicolon) in Steam Input
  8. Open a text editor (e.g. GNOME Text Editor)
  9. Press A on the controller → output is ; instead of :
  10. Press B on the controller → output is ; instead of m

Note: This can be reproduced directly on the desktop without launching a game, making it straightforward to test with any basic text editor.

Llhervier 2026-05-23 github

Reproduced on Bazzite, desktop version.

Llhervier 2026-06-29 github

Update: debugging with xev

Environment

  • OS: Ubuntu 26.04 (fully up to date)
  • Steam client: beta version 1782437068

Test protocol

Button configuration (Desktop Configuration in Steam client):

Button QWERTY Steam Input binding
A . (dot)
B ; (semicolon)
X 1
Y Q

Steps:

  1. Used the Steam client to configure the buttons in Desktop Configuration mode as described above.
  2. Launched xev and focused its window.
  3. Pressed each button on the controller and recorded the resulting X11 events from the log.

Raw xev output

Button A — bound to . (QWERTY dot):

KeyPress event, serial 38, synthetic NO, window 0x3200001,
    root 0x350, subw 0x0, time 235928, (-418,-5), root:(1291,69),
    state 0x10, keycode 59 (keysym 0x3b, semicolon), same_screen YES,
    XLookupString gives 1 bytes: (3b) ";"
    XmbLookupString gives 1 bytes: (3b) ";"
    XFilterEvent returns: False
KeyRelease event, serial 38, synthetic NO, window 0x3200001,
    root 0x350, subw 0x0, time 235972, (-418,-5), root:(1291,69),
    state 0x10, keycode 59 (keysym 0x3b, semicolon), same_screen YES,
    XLookupString gives 1 bytes: (3b) ";"
    XFilterEvent returns: False

Button B — bound to ; (QWERTY semicolon):

KeyPress event, serial 38, synthetic NO, window 0x3200001,
    root 0x350, subw 0x0, time 298564, (36,-28), root:(1745,46),
    state 0x10, keycode 59 (keysym 0x3b, semicolon), same_screen YES,
    XLookupString gives 1 bytes: (3b) ";"
    XmbLookupString gives 1 bytes: (3b) ";"
    XFilterEvent returns: False
KeyRelease event, serial 38, synthetic NO, window 0x3200001,
    root 0x350, subw 0x0, time 298616, (36,-28), root:(1745,46),
    state 0x10, keycode 59 (keysym 0x3b, semicolon), same_screen YES,
    XLookupString gives 1 bytes: (3b) ";"
    XFilterEvent returns: False

Button X — bound to 1:

KeyPress event, serial 38, synthetic NO, window 0x3200001,
    root 0x350, subw 0x0, time 325870, (41,-26), root:(1750,48),
    state 0x10, keycode 10 (keysym 0x26, ampersand), same_screen YES,
    XLookupString gives 1 bytes: (26) "&"
    XmbLookupString gives 1 bytes: (26) "&"
    XFilterEvent returns: False
KeyRelease event, serial 38, synthetic NO, window 0x3200001,
    root 0x350, subw 0x0, time 325926, (41,-26), root:(1750,48),
    state 0x10, keycode 10 (keysym 0x26, ampersand), same_screen YES,
    XLookupString gives 1 bytes: (26) "&"
    XFilterEvent returns: False

Button Y — bound to Q:

KeyPress event, serial 38, synthetic NO, window 0x3200001,
    root 0x350, subw 0x0, time 350103, (40,-25), root:(1749,49),
    state 0x10, keycode 38 (keysym 0x71, q), same_screen YES,
    XLookupString gives 1 bytes: (71) "q"
    XmbLookupString gives 1 bytes: (71) "q"
    XFilterEvent returns: False
KeyRelease event, serial 38, synthetic NO, window 0x3200001,
    root 0x350, subw 0x0, time 350127, (40,-25), root:(1749,49),
    state 0x10, keycode 38 (keysym 0x71, q), same_screen YES,
    XLookupString gives 1 bytes: (71) "q"
    XFilterEvent returns: False

Analysis

Button QWERTY binding Keycode received Keysym received Expected (AZERTY) Result
X 1 10 ampersand & ✅ Correct
A . 59 semicolon : ❌ Wrong
B ; 59 semicolon m ❌ Wrong
Y Q 38 q a ❌ Wrong

Steam Input injects the QWERTY keycode of the bound key, regardless of the system keyboard layout. XKB then correctly translates that keycode — but since the keycode is wrong to begin with, the output character is wrong too.

For button X (working): the 1 key happens to sit at the same physical position in both QWERTY and AZERTY. The keycode (10) is correct and XKB produces the expected &.

For buttons A and B (collision): both . and ; map to keycode 59 in QWERTY, so Steam Input injects the same keycode for both, making it impossible to distinguish them — and impossible to produce the expected AZERTY characters : and m.

For button Y: Steam Input injects keycode 38 (QWERTY Q), which XKB correctly translates to q under AZERTY — but the expected output is a, which sits at that physical position on an AZERTY keyboard.

Llhervier 2026-06-29 github

Below is the complete mapping table for all available Steam Input key bindings, showing the expected AZERTY keycode and the actually injected keycode, along with the resulting character where applicable. Rows marked ✅ are correct, rows marked ❌ are incorrect.

Keycode QWERTY AZERTY (Raw) AZERTY (Shift) AZERTY (AltGr) Obtained Keycode Displayed char Result
9 Esc Esc 9
67 F1 F1 67
68 F2 F2 68
69 F3 F3 69
70 F4 F4 70
71 F5 F5 71
72 F6 F6 72
73 F7 F7 73
74 F8 F8 74
75 F9 F9 75
76 F10 F10 76
95 F11 F11 95
96 F12 F12 96
49 ` œ 33 p
10 1 & 1 10 &
11 2 é 2 ~ 11 é
12 3 " 3 # 12 "
13 4 ' 4 { 13 '
14 5 ( 5 [ 14 (
15 6 - 6 | 15 -
16 7 è 7 ` 16 è
17 8 _ 8 \ 17 _
18 9 ç 9 ^ 18 ç
19 0 à 0 @ 19 à
20 - ) ° ] 15 -
21 = = + } 21 =
22 BCKSPC BCKSPC 22
23 TAB TAB 23
24 Q A 38 q
25 W Z 52 w
26 E E 26 e
27 R R 27 r
28 T T 28 t
29 Y Y 29 y
30 U U 30 u
31 I I 31 i
32 O O 32 o
33 P P 33 p
34 [ ^ " 14 (
35 ] $ £ 20 )
51 \ * µ 17 _
66 CAPS CAPS 66
38 A Q 24 a
39 S S 39 s
40 D D 40 d
41 F F 41 f
42 G G 42 g
43 H H 43 h
44 J J 44 j
45 K K 45 k
46 L L 46 l
47 ; M 59 ;
48 ' ù % 13 '
36 ENTER ENTER 36
50 LSHIFT LSHIFT 50
52 Z W 25 z
53 X X 53 x
54 C C 54 c
55 V V 55 v
56 B B 56 b
57 N N 57 n
58 M , ? 47 m
59 , ; . 58 ,
60 . : / 59 ;
61 / ! § 60 :
62 RSHIFT RSHIFT 62
37 LCTRL LCTRL 37
133 WIN WIN 133
64 LALT LALT 64
65 SPACE SPACE 65
108 RALT RALT NOTHING
105 RCTRL RCTRL 105
118 INSER INSER 118
110 HOME HOME 110
112 PGUP PGUP 112
119 DEL DEL 119
115 END END 115
117 PGDOWN PGDOWN 117
111 UP UP 111
116 DOWN DOWN 116
113 LEFT LEFT 113
114 RIGHT RIGHT 114
77 VERRNUM VERRNUM 77
106 NUMPAD / NUMPAD / 106 /
63 NUMPAD * NUMPAD * 63 *
82 NUMPAD - NUMPAD - 82 -
79 NUMPAD 7 NUMPAD 7 79 7
80 NUMPAD 8 NUMPAD 8 80 8
81 NUMPAD 9 NUMPAD 9 81 9
83 NUMPAD 4 NUMPAD 4 83 4
84 NUMPAD 5 NUMPAD 5 84 5
85 NUMPAD 6 NUMPAD 6 85 6
87 NUMPAD 1 NUMPAD 1 87 1
88 NUMPAD 2 NUMPAD 2 88 2
89 NUMPAD 3 NUMPAD 3 89 3
86 NUMPAD + NUMPAD + 86 +
104 NUMPAD ENTER NUMPAD ENTER 104
90 NUMPAD 0 NUMPAD 0 90 0
91 NUMPAD . NUMPAD . 129 .

Root cause analysis

From this data, the mechanism behind the bug seems clear. For each binding, Steam Input:

  1. Takes the character shown on the QWERTY key (e.g. Q, [, \)
  2. Searches the active system layout (AZERTY) for the key that produces that character, including characters that require a modifier combination (Shift, AltGr)
  3. Extracts the keycode of that key
  4. Injects that keycode via uinput
  5. XKB then translates it normally, producing the base character of that key in AZERTY

This explains almost every incorrect mapping. Here are the detailed cases:

Keycode 24 — QWERTY Q
Steam Input looks up Q in the AZERTY layout and finds it on keycode 38 (the A key). It injects keycode 38 → XKB produces q. Expected: a.

Keycode 25 — QWERTY W
Steam Input looks up W in the AZERTY layout and finds it on keycode 52 (the Z key). It injects keycode 52 → XKB produces w. Expected: z.

Keycode 38 — QWERTY A
Steam Input looks up A in the AZERTY layout and finds it on keycode 24 (the Q key). It injects keycode 24 → XKB produces a. Expected: q.

Keycode 52 — QWERTY Z
Steam Input looks up Z in the AZERTY layout and finds it on keycode 25 (the W key). It injects keycode 25 → XKB produces z. Expected: w.

Keycode 20 — QWERTY -
Steam Input looks up - in the AZERTY layout and finds it as the base character on keycode 15 (the 6 key), ignoring the intended keycode 20 (the ) key). It injects keycode 15 → XKB produces -. Expected: ).

Keycode 34 — QWERTY [
Steam Input looks up [ in the AZERTY layout and finds it on keycode 14 (the ( key, which produces [ when combined with AltGr). It injects keycode 14 → XKB produces (. Expected: ^.

Keycode 35 — QWERTY ]
Steam Input looks up ] in the AZERTY layout and finds it on keycode 20 (the ) key, which produces ] when combined with AltGr). It injects keycode 20 → XKB produces ). Expected: $.

Keycode 47 — QWERTY ;
Steam Input looks up ; in the AZERTY layout and finds it as the base character on keycode 59 (the . key). It injects keycode 59 → XKB produces ;. Expected: m.

Keycode 48 — QWERTY '
Steam Input looks up ' in the AZERTY layout and finds it as the base character on keycode 13 (the 4 key). It injects keycode 13 → XKB produces '. Expected: ù.

Keycode 51 — QWERTY \
Steam Input looks up \ in the AZERTY layout and finds it on keycode 17 (the 8 key, which produces \ when combined with AltGr). It injects keycode 17 → XKB produces _. Expected: *.

Keycode 58 — QWERTY M
Steam Input looks up m in the AZERTY layout and finds it as the base character on keycode 47. It injects keycode 47 → XKB produces m. Expected: ,.

Keycode 61 — QWERTY /
Steam Input looks up / in the AZERTY layout and finds it on keycode 60 (the : key, which produces / when combined with Shift). It injects keycode 60 → XKB produces :. Expected: !.


Two additional cases fall outside this pattern:

Keycode 49 — QWERTY `
Following the same mechanism, Steam Input should look up ` in the AZERTY layout, find it via AltGr+è (keycode 16), and inject keycode 16 → XKB would produce è. Instead, Steam Input injects keycode 33 (the P key) → XKB produces p. The expected fallback behavior does not occur, and the result has no obvious logic.

Keycode 91 — Numpad .
Steam Input injects keycode 129 instead of 91. Despite this, XKB correctly produces ., so the character output is correct. However, the use of a non-standard keycode for this key remains unexplained.


One additional case is worth noting separately:

Keycode 108 — RAlt
Steam Input produces no event at all.

Nothing extracted yet.