Repository: cloudhead/rx (language: rust)

Issue:
Modifier keys + different keyboard layouts causing issues with keymappings
Related to #18 

Windows 10, compiled with 1.38 stable. (`--vulkan` feature flag)

rx does not go into command mode when writing `:` using a [Swedish/Nordic keyboard layout](https://nordickeyboards.se/info_sheets/das_keyboard_nordic_layout.jpg) (Shift + Period) and instead zooms in one level.

It seems to recieve a colon in the `RecievedCharacter` event, but the `KeyboardInput` contains a period.

Logs:
```
[2019-09-30T13:57:01Z DEBUG rx] event: KeyboardInput(KeyboardInput { state: Pressed, key: Some(Shift), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } })
[2019-09-30T13:57:01Z DEBUG rx::session] KeyboardInput { state: Pressed, key: Some(Shift), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } }
[2019-09-30T13:57:01Z DEBUG rx::session] command: BrushSet(Multi)
[2019-09-30T13:57:02Z DEBUG rx] event: KeyboardInput(KeyboardInput { state: Pressed, key: Some(Period), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } })
[2019-09-30T13:57:02Z DEBUG rx] event: ReceivedCharacter(':')
[2019-09-30T13:57:02Z DEBUG rx::session] KeyboardInput { state: Pressed, key: Some(Period), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } }
[2019-09-30T13:57:02Z DEBUG rx::session] command: Zoom(Incr)
[2019-09-30T13:57:02Z DEBUG rx::session] zoom: 1 -> 2
[2019-09-30T13:57:02Z DEBUG rx] event: KeyboardInput(KeyboardInput { state: Released, key: Some(Period), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } })
[2019-09-30T13:57:02Z DEBUG rx::session] KeyboardInput { state: Released, key: Some(Period), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } }
[2019-09-30T13:57:02Z DEBUG rx] event: KeyboardInput(KeyboardInput { state: Released, key: Some(Shift), modifiers: ModifiersState { shift: false, ctrl: false, alt: false, meta: false } })
[2019-09-30T13:57:02Z DEBUG rx::session] KeyboardInput { state: Released, key: Some(Shift), modifiers: ModifiersState { shift: false, ctrl: false, alt: false, meta: false } }
[2019-09-30T13:57:02Z DEBUG rx::session] command: BrushUnset(Multi)
```


Discussion / hints:
Tried recompiling with the `winit` feature, same result:

```
[2019-09-30T14:22:47Z DEBUG rx] event: KeyboardInput(KeyboardInput { state: Pressed, key: Some(Shift), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } })
[2019-09-30T14:22:47Z DEBUG rx::session] KeyboardInput { state: Pressed, key: Some(Shift), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } }
[2019-09-30T14:22:47Z DEBUG rx::session] command: BrushSet(Multi)
[2019-09-30T14:22:48Z DEBUG rx] event: KeyboardInput(KeyboardInput { state: Pressed, key: Some(Period), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } })
[2019-09-30T14:22:48Z DEBUG rx] event: ReceivedCharacter(':')
[2019-09-30T14:22:48Z DEBUG rx::session] KeyboardInput { state: Pressed, key: Some(Period), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } }
[2019-09-30T14:22:48Z DEBUG rx::session] command: Zoom(Incr)
[2019-09-30T14:22:48Z DEBUG rx::session] zoom: 1 -> 2
[2019-09-30T14:22:48Z DEBUG rx] event: KeyboardInput(KeyboardInput { state: Released, key: Some(Period), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } })
[2019-09-30T14:22:48Z DEBUG rx::session] KeyboardInput { state: Released, key: Some(Period), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } }
[2019-09-30T14:22:48Z DEBUG rx] event: KeyboardInput(KeyboardInput { state: Released, key: Some(Shift), modifiers: ModifiersState { shift: false, ctrl: false, alt: false, meta: false } })
[2019-09-30T14:22:48Z DEBUG rx::session] KeyboardInput { state: Released, key: Some(Shift), modifiers: ModifiersState { shift: false, ctrl: false, alt: false, meta: false } }
[2019-09-30T14:22:48Z DEBUG rx::session] command: BrushUnset(Multi)
```
Thanks for the log output, this should help. In the meantime, you can remap anything by editing the `init.rx` file in eg. `C:\Users\<User>\AppData\`.
What if `ReceivedCharacter` was used for any key that's not a modifier key (in which case it would still use `KeyboardInput`)? Or perhaps there is a way to correctly translate the key to the right key+modifier combination?

Also for anyone looking at this, on Linux the path is `~/.config/init.rx`, and the default options are in [`rx/init.rx`](https://github.com/cloudhead/rx/blob/master/config/init.rx) (I think it would be great to link to this file in [the guide](https://rx.cloudhead.io/guide.html) so people know what they can do).

It's weird that the following `init.rx` doesn't work when I press <kbd>Shift + .</kbd> (<kbd>:</kbd>) though:
```
map . :mode command
```

Because on the verbose log I see:
```
DEBUG [rx] event: KeyboardInput(KeyboardInput { state: Pressed, key: Some(Period), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } })
DEBUG [rx] event: ReceivedCharacter(':')
DEBUG [rx] event: KeyboardInput(KeyboardInput { state: Released, key: Some(Period), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } })
DEBUG [rx] event: KeyboardInput(KeyboardInput { state: Released, key: Some(Shift), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } })
```

Note the `state: Pressed, key: Some(Period)`.
It seems quite similar to https://github.com/cloudhead/rx/issues/66 which I fixed some time ago. Could you try checking that commit out and seeing if that solves the problem?

https://github.com/cloudhead/rx/commit/73f26c2b0b5c216caba8b049e478e5f02e7923f8
I cloned this today, so I was on https://github.com/cloudhead/rx/commit/2dda631ddf921d7c8b07f49b9464c2db0f8f4baa, and seems pressing `:` on my keyboard didn't work. Let me know what I should try or what additional information you need and I'll be glad to help.
So the reason it's not working for you is that this behavior is currently hard-coded: when `rx` receives `<shift> ;`, it switches to command mode, as well as when it received simply `:`. 

Eventually I'd like to support modifiers in key-mappings, which should allow you to do:

```
map     <shift> .   :mode command
```
> What if `ReceivedCharacter` was used for any key that's not a modifier key (in which case it would still use `KeyboardInput`)? Or perhaps there is a way to correctly translate the key to the right key+modifier combination?

I need to look into this. I remember trying it out back in the day, and having other problems. Otherwise, I think this could indeed be a good solution.
Ok I remember the issue: if we use `ReceivedCharacter` to map keys, we don't get `Release` events. Which means we can't express things like "hold <kbd>e</kbd> to erase". This is a big limitation.
> Eventually I'd like to support modifiers in key-mappings

The `init.rx` [I linked above](https://github.com/cloudhead/rx/issues/23#issuecomment-620558090) made it seem like mappings for modes were already supported but I guess there is more magic going on if you say that :P 

> if we use `ReceivedCharacter` to map […] we can't express things like "hold <kbd>e</kbd> to erase". 

Ah yeah that's a big limitation, not really actionable (perhaps a comment could say that in the code though?).
> The `init.rx` [I linked above](https://github.com/cloudhead/rx/issues/23#issuecomment-620558090) made it seem like mappings for modes were already supported but I guess there is more magic going on if you say that :P
> 
> >

It should work, but looking at the debug output, you're holding `<shift>` when pressing `.`, so it won't trigger the mapping.
Yes, I am holding <kbd>Shift</kbd> while pressing <kbd>.</kbd> because that's <kbd>:</kbd> in my keyboard.

![image](https://user-images.githubusercontent.com/6297805/81272126-57508e00-904d-11ea-86bc-b3b2b2db365e.png)

<sup>I should clean my keyboard</sup>

> Eventually I'd like to support modifiers in key-mappings

Oh! Now I read that properly, I missed the "modifiers" part and the `<shift>` addition. That makes more sense (I had somehow understood just custom mappings, which *are* already supported). Anyway, I tried on latest master (8e8deb5bb6db071212795938e68feaf28e19797b) with no luck again.

<details><summary>Verbose log again, if you need it</summary>

```
DEBUG [rx] options: Options { width: 1280, height: 720, resizable: true, headless: false, source: None, exec: Normal, glyphs: [137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 128, 0, 0, 0, 112, 8, 6, 0, 0, 0, 214, 172, 120, 210, 0, 0, 12, 20, 73, 68, 65, 84, 120, 156, 237, 152, 91, 114, 227, 200, 18, 67, 231, 174, 118, 214, 212, 171, 189, 3, 100, 4, 42, 80, 201, 172, 7, 41, 217, 178, 155, 60, 31, 6, 18, 200, 42, 73, 166, 166, 29, 49, 255, 251, 231, 7, 241, 239, 191, 255, 254, 255, 207, 159, 63, 63, 234, 61, 253, 173, 232, 119, 125, 234, 151, 173, 67, 176, 7, 188, 115, 47, 148, 73, 17, 29, 152, 117, 59, 240, 60, 228, 159, 209, 29, 236, 71, 157, 24, 237, 140, 242, 223, 138, 62, 207, 169, 15, 164, 67, 176, 7, 188, 115, 47, 148, 73, 17, 53, 152, 65, 58, 124, 71, 189, 50, 206, 242, 194, 51, 247, 206, 40, 23, 175, 246, 191, 5, 255, 28, 241, 99, 23, 63, 232, 120, 238, 222, 81, 46, 69, 20, 248, 92, 121, 41, 162, 50, 19, 158, 185, 23, 85, 230, 172, 122, 49, 219, 155, 117, 100, 213, 127, 23, 254, 62, 226, 199, 46, 126, 112, 23, 158, 129, 148, 240, 46, 246, 84, 140, 177, 43, 239, 48, 135, 28, 246, 51, 236, 32, 177, 7, 57, 5, 207, 238, 156, 203, 123, 62, 143, 188, 168, 178, 239, 38, 191, 135, 102, 118, 200, 135, 207, 160, 179, 82, 68, 129, 207, 238, 71, 140, 118, 152, 67, 190, 244, 225, 139, 188, 175, 57, 43, 170, 142, 81, 254, 157, 228, 247, 208, 204, 14, 249, 240, 25, 116, 86, 138, 168, 193, 12, 210, 145, 119, 8, 247, 86, 185, 188, 20, 209, 146, 51, 187, 164, 218, 103, 6, 9, 188, 243, 60, 227, 123, 51, 70, 119, 236, 158, 23, 188, 39, 159, 233, 134, 21, 213, 5, 43, 120, 6, 82, 146, 239, 226, 174, 50, 247, 162, 202, 72, 206, 243, 188, 226, 213, 125, 205, 89, 81, 117, 140, 242, 239, 162, 122, 253, 110, 248, 74, 244, 226, 82, 68, 7, 188, 115, 79, 242, 236, 228, 46, 207, 43, 94, 217, 223, 241, 162, 202, 190, 139, 209, 107, 31, 130, 25, 163, 75, 118, 208, 89, 41, 162, 192, 231, 145, 39, 154, 169, 24, 187, 127, 61, 152, 205, 230, 29, 118, 207, 204, 246, 102, 29, 153, 245, 236, 32, 221, 231, 58, 195, 234, 60, 251, 170, 59, 4, 51, 70, 151, 236, 160, 179, 82, 68, 13, 102, 144, 14, 223, 97, 207, 89, 138, 168, 101, 176, 1, 103, 72, 192, 156, 51, 21, 227, 22, 187, 251, 187, 123, 103, 225, 189, 144, 120, 239, 144, 211, 204, 206, 179, 171, 114, 82, 134, 159, 98, 244, 70, 61, 167, 135, 148, 31, 244, 85, 120, 247, 236, 222, 85, 255, 42, 95, 117, 255, 236, 222, 50, 252, 20, 179, 55, 250, 93, 140, 222, 195, 40, 127, 23, 95, 125, 255, 136, 111, 127, 193, 135, 159, 197, 243, 5, 184, 57, 207, 23, 224, 38, 240, 79, 12, 164, 161, 63, 55, 241, 131, 140, 22, 50, 220, 171, 58, 230, 144, 70, 222, 89, 245, 14, 119, 189, 231, 12, 57, 48, 219, 153, 117, 98, 182, 227, 29, 89, 245, 239, 132, 175, 117, 230, 254, 213, 126, 213, 43, 139, 80, 3, 108, 99, 148, 65, 14, 31, 158, 249, 44, 115, 47, 170, 140, 48, 135, 116, 175, 193, 204, 231, 204, 170, 175, 240, 51, 238, 133, 103, 238, 69, 149, 189, 131, 171, 247, 206, 206, 85, 157, 178, 8, 53, 192, 6, 156, 33, 229, 67, 144, 34, 106, 236, 102, 78, 213, 43, 147, 34, 10, 242, 156, 89, 245, 153, 188, 159, 103, 226, 153, 123, 49, 202, 32, 221, 239, 77, 204, 58, 193, 157, 170, 103, 14, 57, 144, 119, 185, 151, 179, 17, 218, 141, 101, 13, 176, 205, 75, 17, 181, 12, 182, 243, 35, 174, 236, 248, 236, 158, 112, 134, 116, 156, 233, 51, 220, 247, 62, 207, 196, 51, 247, 98, 148, 65, 202, 215, 158, 117, 132, 253, 168, 59, 195, 206, 61, 190, 19, 63, 20, 72, 17, 181, 12, 182, 99, 148, 139, 89, 207, 14, 18, 248, 14, 243, 217, 92, 225, 59, 238, 69, 149, 145, 89, 14, 105, 228, 157, 220, 147, 188, 115, 21, 222, 253, 174, 187, 200, 236, 190, 220, 133, 97, 8, 233, 62, 16, 51, 159, 197, 40, 39, 179, 46, 227, 187, 244, 144, 3, 234, 43, 120, 70, 189, 123, 81, 101, 164, 202, 119, 51, 103, 213, 159, 229, 93, 247, 205, 238, 169, 186, 24, 170, 162, 202, 200, 217, 124, 196, 108, 127, 214, 9, 223, 113, 47, 118, 51, 82, 229, 85, 230, 172, 250, 43, 140, 238, 100, 14, 57, 144, 119, 185, 151, 179, 21, 177, 92, 29, 172, 50, 82, 229, 85, 230, 84, 125, 149, 137, 220, 229, 153, 120, 230, 94, 236, 102, 164, 202, 171, 76, 140, 58, 230, 144, 195, 131, 33, 179, 206, 225, 222, 106, 167, 98, 231, 92, 181, 211, 6, 150, 144, 70, 94, 20, 220, 203, 29, 51, 200, 1, 223, 203, 59, 222, 101, 184, 155, 123, 102, 144, 198, 217, 158, 112, 167, 202, 9, 59, 72, 35, 239, 121, 159, 59, 161, 157, 170, 159, 117, 25, 238, 238, 236, 137, 221, 253, 106, 175, 27, 30, 190, 150, 234, 1, 124, 154, 31, 245, 102, 254, 102, 126, 226, 195, 39, 63, 238, 13, 61, 124, 47, 207, 23, 224, 230, 196, 23, 128, 255, 60, 65, 58, 126, 226, 63, 87, 15, 239, 167, 251, 191, 127, 78, 206, 57, 67, 26, 179, 142, 120, 79, 242, 142, 247, 179, 142, 228, 94, 104, 111, 213, 147, 188, 147, 59, 159, 137, 103, 244, 144, 14, 117, 25, 223, 213, 14, 51, 121, 225, 153, 251, 204, 168, 243, 220, 189, 200, 25, 103, 72, 67, 93, 247, 5, 160, 135, 52, 60, 151, 23, 158, 185, 23, 158, 185, 23, 158, 185, 39, 171, 57, 115, 165, 247, 204, 189, 216, 205, 70, 248, 174, 123, 178, 154, 29, 118, 144, 246, 44, 72, 206, 242, 76, 152, 105, 118, 47, 148, 181, 47, 128, 20, 93, 224, 179, 123, 225, 153, 123, 225, 153, 251, 138, 170, 247, 204, 125, 197, 87, 244, 187, 217, 8, 223, 29, 121, 81, 101, 66, 221, 72, 177, 18, 59, 144, 231, 11, 0, 91, 114, 165, 223, 205, 70, 248, 174, 188, 20, 81, 199, 40, 39, 222, 173, 188, 20, 81, 203, 96, 59, 47, 148, 181, 67, 82, 116, 129, 207, 244, 144, 3, 179, 94, 29, 97, 239, 115, 166, 234, 61, 163, 135, 52, 148, 139, 157, 94, 25, 61, 36, 168, 50, 71, 189, 224, 94, 206, 70, 248, 46, 61, 228, 112, 159, 96, 191, 211, 173, 188, 20, 81, 203, 96, 59, 47, 148, 181, 67, 82, 116, 81, 66, 218, 27, 230, 44, 47, 60, 115, 47, 60, 115, 95, 81, 245, 158, 185, 175, 184, 210, 123, 230, 94, 236, 102, 35, 124, 87, 94, 138, 168, 99, 148, 147, 81, 231, 121, 229, 165, 136, 90, 6, 219, 80, 118, 88, 132, 196, 131, 167, 167, 98, 140, 92, 94, 120, 230, 94, 120, 230, 190, 162, 234, 61, 115, 95, 113, 165, 247, 204, 189, 216, 205, 72, 149, 123, 54, 242, 162, 202, 196, 168, 243, 188, 242, 82, 68, 45, 131, 109, 40, 235, 22, 71, 84, 59, 158, 185, 23, 158, 185, 23, 158, 185, 39, 171, 57, 115, 165, 247, 204, 189, 216, 205, 72, 149, 123, 230, 158, 172, 102, 103, 212, 121, 238, 158, 112, 134, 196, 127, 200, 144, 152, 229, 133, 178, 8, 57, 64, 14, 112, 1, 18, 189, 188, 240, 140, 30, 210, 161, 78, 228, 29, 239, 103, 29, 201, 189, 208, 30, 123, 249, 17, 220, 129, 52, 124, 159, 157, 207, 196, 51, 122, 72, 135, 58, 145, 119, 188, 103, 231, 51, 241, 140, 30, 114, 128, 61, 59, 42, 198, 14, 207, 221, 19, 206, 144, 56, 15, 9, 148, 9, 117, 241, 227, 225, 190, 60, 95, 128, 155, 243, 124, 1, 110, 206, 243, 5, 248, 1, 240, 239, 179, 254, 38, 127, 55, 31, 121, 209, 187, 194, 7, 13, 9, 252, 129, 51, 247, 185, 130, 59, 144, 96, 180, 203, 157, 81, 55, 34, 150, 121, 16, 210, 145, 47, 202, 59, 222, 231, 142, 120, 79, 242, 142, 250, 156, 59, 190, 35, 47, 60, 115, 47, 148, 73, 17, 117, 120, 238, 94, 40, 147, 34, 234, 240, 156, 30, 114, 64, 189, 224, 94, 206, 196, 172, 115, 70, 123, 202, 165, 136, 182, 136, 197, 234, 144, 103, 238, 133, 103, 238, 133, 103, 238, 197, 110, 70, 170, 220, 51, 247, 66, 153, 20, 81, 135, 231, 238, 133, 50, 41, 162, 142, 81, 46, 170, 190, 202, 196, 172, 115, 170, 189, 156, 229, 121, 70, 44, 85, 7, 60, 115, 47, 60, 115, 47, 60, 115, 63, 99, 180, 87, 229, 158, 185, 23, 202, 164, 136, 90, 6, 219, 121, 226, 179, 123, 178, 154, 51, 163, 126, 148, 139, 85, 79, 118, 118, 206, 16, 23, 85, 151, 122, 230, 94, 120, 230, 94, 120, 230, 126, 198, 104, 175, 202, 61, 115, 47, 148, 73, 17, 181, 12, 182, 243, 196, 103, 247, 100, 53, 59, 87, 59, 178, 234, 201, 206, 206, 25, 226, 34, 94, 10, 233, 240, 23, 97, 239, 51, 241, 140, 30, 210, 161, 78, 228, 157, 220, 19, 238, 236, 230, 57, 243, 249, 172, 23, 204, 32, 211, 247, 38, 69, 116, 96, 214, 145, 85, 79, 86, 59, 171, 254, 44, 113, 209, 234, 82, 239, 229, 165, 136, 90, 6, 219, 249, 25, 213, 94, 149, 145, 42, 207, 153, 207, 149, 31, 41, 86, 2, 159, 221, 59, 163, 156, 204, 58, 241, 142, 157, 85, 127, 150, 184, 104, 117, 169, 247, 242, 82, 68, 45, 131, 13, 242, 92, 81, 237, 84, 25, 169, 242, 156, 249, 236, 158, 172, 102, 226, 153, 123, 231, 108, 158, 121, 199, 222, 172, 187, 66, 92, 180, 186, 52, 247, 171, 153, 120, 230, 94, 236, 102, 164, 202, 71, 25, 228, 240, 79, 56, 115, 207, 86, 51, 217, 205, 200, 40, 207, 188, 99, 111, 214, 93, 33, 46, 218, 185, 148, 59, 144, 128, 187, 156, 169, 24, 163, 147, 23, 57, 227, 12, 105, 120, 39, 184, 83, 229, 132, 29, 164, 81, 237, 105, 39, 119, 204, 61, 91, 205, 100, 55, 35, 204, 33, 7, 242, 46, 247, 114, 54, 98, 180, 59, 202, 175, 114, 249, 162, 119, 191, 145, 59, 192, 223, 25, 36, 88, 253, 238, 184, 235, 59, 156, 33, 129, 231, 43, 252, 92, 197, 246, 69, 15, 127, 39, 207, 23, 224, 230, 60, 95, 128, 155, 19, 95, 0, 254, 157, 56, 243, 119, 37, 243, 234, 249, 138, 43, 119, 242, 12, 36, 56, 115, 150, 231, 118, 247, 87, 187, 85, 95, 101, 25, 238, 64, 78, 189, 239, 119, 208, 94, 140, 111, 224, 234, 139, 191, 114, 118, 196, 217, 59, 207, 238, 59, 103, 206, 174, 118, 171, 190, 202, 42, 118, 247, 222, 73, 247, 98, 87, 223, 192, 213, 115, 51, 206, 222, 121, 118, 223, 121, 229, 236, 14, 187, 247, 239, 238, 189, 147, 237, 23, 227, 155, 131, 52, 252, 141, 178, 227, 76, 197, 24, 112, 134, 52, 188, 35, 171, 158, 248, 78, 238, 189, 35, 236, 115, 230, 176, 135, 116, 104, 159, 29, 61, 21, 99, 192, 25, 210, 152, 117, 100, 214, 179, 203, 89, 69, 181, 199, 12, 114, 192, 247, 170, 29, 239, 73, 222, 81, 31, 63, 174, 192, 11, 117, 9, 61, 164, 93, 74, 152, 105, 118, 47, 60, 115, 47, 60, 115, 47, 60, 163, 135, 116, 168, 35, 236, 125, 38, 158, 209, 67, 150, 103, 200, 40, 23, 85, 159, 179, 60, 139, 42, 247, 108, 199, 11, 207, 220, 11, 101, 93, 56, 131, 7, 32, 29, 188, 0, 18, 157, 188, 240, 204, 125, 69, 213, 123, 230, 94, 228, 44, 207, 78, 213, 121, 230, 94, 84, 25, 25, 229, 162, 234, 61, 115, 159, 169, 58, 207, 118, 188, 240, 204, 189, 80, 214, 133, 35, 180, 12, 219, 240, 204, 189, 240, 204, 125, 69, 213, 123, 70, 15, 57, 160, 158, 112, 199, 103, 167, 234, 60, 115, 47, 170, 140, 140, 114, 81, 245, 204, 32, 65, 238, 28, 238, 229, 222, 179, 29, 47, 60, 115, 47, 148, 117, 225, 8, 45, 195, 54, 60, 115, 47, 60, 115, 95, 81, 245, 158, 185, 31, 49, 219, 169, 58, 207, 220, 139, 42, 35, 163, 92, 84, 189, 103, 238, 51, 85, 231, 217, 142, 23, 158, 185, 23, 202, 186, 112, 132, 150, 97, 3, 206, 144, 246, 109, 230, 44, 47, 60, 115, 47, 60, 115, 79, 56, 67, 166, 247, 103, 102, 59, 185, 227, 12, 153, 222, 95, 101, 100, 148, 139, 170, 207, 89, 158, 69, 149, 123, 182, 227, 133, 103, 238, 133, 178, 46, 156, 193, 3, 144, 128, 7, 57, 83, 49, 118, 157, 80, 39, 242, 206, 172, 103, 199, 153, 138, 49, 224, 12, 233, 200, 189, 207, 25, 246, 144, 128, 123, 156, 169, 24, 163, 147, 23, 57, 227, 12, 233, 240, 94, 112, 47, 231, 187, 25, 97, 14, 9, 216, 115, 166, 98, 140, 110, 229, 69, 206, 56, 67, 26, 234, 226, 199, 29, 225, 47, 68, 191, 132, 119, 242, 85, 247, 126, 21, 191, 230, 141, 190, 155, 175, 122, 80, 95, 117, 239, 87, 241, 107, 222, 232, 171, 240, 193, 64, 26, 239, 124, 72, 126, 247, 59, 239, 253, 14, 126, 213, 155, 125, 120, 63, 63, 234, 11, 192, 255, 146, 126, 219, 127, 65, 103, 225, 103, 132, 4, 249, 179, 178, 203, 25, 97, 14, 9, 170, 254, 21, 222, 122, 217, 171, 240, 131, 190, 251, 3, 254, 36, 86, 159, 111, 213, 147, 157, 157, 51, 188, 237, 162, 119, 240, 238, 15, 247, 211, 88, 125, 190, 85, 79, 118, 118, 206, 16, 23, 233, 82, 42, 198, 128, 51, 164, 225, 157, 208, 14, 59, 122, 42, 198, 6, 51, 72, 48, 235, 200, 172, 103, 231, 51, 241, 204, 189, 80, 38, 69, 212, 225, 185, 123, 225, 153, 123, 225, 25, 61, 228, 128, 122, 193, 189, 156, 57, 171, 158, 236, 236, 156, 33, 46, 226, 165, 144, 238, 13, 51, 211, 236, 94, 120, 70, 15, 57, 117, 190, 98, 180, 87, 229, 158, 185, 23, 202, 164, 136, 58, 60, 119, 47, 60, 115, 47, 170, 76, 140, 186, 81, 46, 86, 61, 169, 118, 152, 65, 134, 191, 255, 25, 177, 80, 45, 123, 230, 94, 120, 230, 190, 98, 213, 139, 209, 94, 149, 123, 230, 94, 40, 147, 34, 106, 25, 108, 231, 137, 207, 238, 73, 158, 73, 149, 145, 81, 78, 102, 29, 89, 245, 36, 239, 248, 60, 242, 51, 98, 161, 90, 246, 204, 189, 240, 204, 253, 8, 238, 64, 26, 213, 62, 119, 118, 243, 156, 249, 124, 214, 11, 102, 144, 195, 123, 99, 254, 74, 6, 9, 114, 151, 225, 238, 106, 135, 112, 15, 210, 240, 51, 234, 60, 155, 17, 75, 60, 148, 15, 120, 230, 94, 120, 230, 126, 151, 234, 76, 149, 145, 42, 207, 153, 207, 149, 31, 41, 86, 2, 159, 221, 147, 60, 147, 156, 229, 185, 98, 181, 179, 234, 201, 206, 206, 25, 226, 162, 234, 82, 207, 220, 19, 206, 144, 246, 45, 227, 44, 191, 75, 117, 166, 202, 72, 206, 57, 67, 218, 235, 139, 89, 238, 217, 106, 38, 158, 185, 39, 156, 33, 237, 117, 56, 203, 207, 88, 237, 173, 122, 178, 179, 115, 134, 184, 136, 151, 66, 58, 242, 139, 248, 14, 59, 206, 84, 140, 209, 201, 87, 176, 135, 116, 140, 246, 181, 155, 123, 229, 132, 29, 103, 42, 198, 6, 51, 72, 244, 144, 6, 115, 207, 86, 51, 201, 25, 103, 72, 192, 156, 51, 21, 99, 215, 57, 234, 5, 247, 114, 230, 172, 122, 178, 179, 115, 134, 184, 232, 202, 165, 87, 206, 252, 77, 92, 249, 252, 171, 51, 171, 158, 236, 236, 156, 33, 46, 186, 114, 233, 149, 51, 127, 19, 87, 63, 63, 207, 65, 130, 124, 158, 93, 206, 8, 115, 72, 80, 245, 43, 252, 188, 195, 187, 226, 50, 46, 112, 128, 221, 230, 202, 153, 223, 12, 63, 47, 164, 241, 155, 62, 123, 126, 239, 130, 159, 161, 251, 91, 118, 5, 157, 159, 41, 214, 134, 112, 7, 242, 240, 33, 226, 1, 189, 250, 16, 116, 71, 165, 168, 167, 112, 15, 242, 240, 33, 226, 33, 189, 242, 16, 116, 126, 166, 88, 27, 194, 29, 200, 195, 135, 136, 7, 244, 234, 67, 208, 29, 149, 162, 158, 194, 61, 200, 195, 135, 136, 135, 180, 122, 8, 121, 199, 103, 249, 153, 98, 109, 8, 119, 32, 15, 31, 34, 30, 208, 206, 67, 208, 158, 20, 81, 67, 89, 165, 168, 167, 112, 15, 242, 240, 33, 226, 33, 237, 62, 132, 106, 87, 217, 76, 177, 54, 132, 59, 144, 135, 15, 17, 15, 104, 231, 33, 104, 79, 138, 168, 161, 172, 82, 212, 83, 184, 7, 121, 248, 16, 241, 144, 86, 15, 33, 239, 248, 44, 63, 83, 172, 13, 225, 14, 228, 225, 67, 196, 3, 122, 245, 33, 232, 142, 74, 81, 79, 225, 30, 228, 225, 67, 196, 67, 122, 229, 33, 232, 252, 76, 177, 54, 132, 59, 144, 135, 15, 17, 15, 232, 213, 135, 160, 59, 42, 69, 61, 133, 123, 144, 135, 15, 17, 15, 233, 149, 135, 160, 243, 51, 197, 218, 16, 238, 64, 30, 62, 68, 60, 160, 87, 31, 130, 238, 168, 20, 245, 20, 238, 65, 30, 62, 196, 243, 203, 191, 57, 207, 23, 224, 230, 60, 95, 128, 155, 243, 124, 1, 110, 206, 243, 5, 184, 57, 207, 23, 224, 230, 60, 95, 128, 155, 243, 124, 1, 110, 206, 243, 5, 184, 57, 207, 23, 224, 230, 60, 95, 128, 155, 243, 124, 1, 110, 206, 243, 5, 184, 57, 207, 23, 224, 230, 252, 7, 152, 197, 39, 194, 187, 212, 22, 109, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130], debug: false }
INFO  [rx] framebuffer size: 1280x720
INFO  [rx] scale factor: 1
DEBUG [rx::session] state: Initializing -> Running
DEBUG [rx::session] command: Noop
DEBUG [rx::session] command: Noop
DEBUG [rx::session] command: Map(KeyMapping { key: Semicolon, press: Mode(Command), release: None, modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: V, press: Mode(Visual(Selecting { dragging: false })), release: None, modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: Period, press: Zoom(Incr), release: None, modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: Comma, press: Zoom(Decr), release: None, modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: Slash, press: Zoom(Set(1.0)), release: None, modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: J, press: ViewPrev, release: None, modes: [Normal] })
DEBUG [rx::session] command: Map(KeyMapping { key: K, press: ViewNext, release: None, modes: [Normal] })
DEBUG [rx::session] command: Map(KeyMapping { key: Tab, press: ViewPrev, release: None, modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: Z, press: ViewCenter, release: None, modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: U, press: Undo, release: None, modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: R, press: Redo, release: None, modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: X, press: SwapColors, release: None, modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: B, press: Tool(Brush(Brush { size: 1, state: NotDrawing, stroke: [], color: Rgba8 { r: 0, g: 0, b: 0, a: 0 }, modes: {}, curr: Point2 { x: 0, y: 0 }, prev: Point2 { x: 0, y: 0 } })), release: None, modes: [Normal] })
DEBUG [rx::session] command: Map(KeyMapping { key: E, press: BrushSet(Erase), release: Some(BrushUnset(Erase)), modes: [Normal] })
DEBUG [rx::session] command: Map(KeyMapping { key: Shift, press: BrushSet(Multi), release: Some(BrushUnset(Multi)), modes: [Normal] })
DEBUG [rx::session] command: Map(KeyMapping { key: Equal, press: BrushToggle(Perfect), release: None, modes: [Normal] })
DEBUG [rx::session] command: Map(KeyMapping { key: RBracket, press: BrushSize(Incr), release: None, modes: [Normal] })
DEBUG [rx::session] command: Map(KeyMapping { key: LBracket, press: BrushSize(Decr), release: None, modes: [Normal] })
DEBUG [rx::session] command: Map(KeyMapping { key: Control, press: Tool(Sampler), release: Some(ToolPrev), modes: [Normal] })
DEBUG [rx::session] command: Map(KeyMapping { key: Space, press: Tool(Pan(NotPanning)), release: Some(ToolPrev), modes: [Help] })
DEBUG [rx::session] command: Map(KeyMapping { key: Space, press: Tool(Pan(NotPanning)), release: Some(ToolPrev), modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: Up, press: Pan(0, 1), release: None, modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: Down, press: Pan(0, -1), release: None, modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: Left, press: Pan(-1, 0), release: None, modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: Right, press: Pan(1, 0), release: None, modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: Return, press: FrameAdd, release: None, modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: Backspace, press: FrameRemove, release: None, modes: [Normal, Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: H, press: FramePrev, release: None, modes: [Normal] })
DEBUG [rx::session] command: Map(KeyMapping { key: L, press: FrameNext, release: None, modes: [Normal] })
DEBUG [rx::session] command: Noop
DEBUG [rx::session] command: Map(KeyMapping { key: J, press: SelectionMove(0, -1), release: None, modes: [Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: K, press: SelectionMove(0, 1), release: None, modes: [Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: H, press: SelectionMove(-1, 0), release: None, modes: [Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: L, press: SelectionMove(1, 0), release: None, modes: [Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: RBracket, press: SelectionOffset(1, 1), release: None, modes: [Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: LBracket, press: SelectionOffset(-1, -1), release: None, modes: [Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: Backslash, press: SelectionExpand, release: None, modes: [Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: F, press: SelectionFill(None), release: None, modes: [Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: Y, press: SelectionYank, release: None, modes: [Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: D, press: SelectionCut, release: None, modes: [Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: E, press: SelectionErase, release: None, modes: [Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: P, press: SelectionPaste, release: None, modes: [Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: W, press: SelectionJump(Forward), release: None, modes: [Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Map(KeyMapping { key: B, press: SelectionJump(Backward), release: None, modes: [Visual(Selecting { dragging: false }), Visual(Pasting)] })
DEBUG [rx::session] command: Noop
DEBUG [rx::session] command: Set("scale", F64(1.0))
DEBUG [rx::session] command: Set("checker", Bool(false))
DEBUG [rx::session] command: Set("debug", Bool(false))
DEBUG [rx::session] command: Set("vsync", Bool(false))
DEBUG [rx::session] command: Set("background", Rgba8(Rgba8 { r: 0, g: 0, b: 0, a: 0 }))
DEBUG [rx::session] command: Noop
DEBUG [rx::session] command: BrushSet(Perfect)
DEBUG [rx::session] command: Noop
DEBUG [rx::session] command: PaletteAdd(Rgba8 { r: 26, g: 28, b: 44, a: 255 })
DEBUG [rx::session] command: PaletteAdd(Rgba8 { r: 93, g: 39, b: 93, a: 255 })
DEBUG [rx::session] command: PaletteAdd(Rgba8 { r: 177, g: 62, b: 83, a: 255 })
DEBUG [rx::session] command: PaletteAdd(Rgba8 { r: 239, g: 125, b: 87, a: 255 })
DEBUG [rx::session] command: PaletteAdd(Rgba8 { r: 255, g: 205, b: 117, a: 255 })
DEBUG [rx::session] command: PaletteAdd(Rgba8 { r: 167, g: 240, b: 112, a: 255 })
DEBUG [rx::session] command: PaletteAdd(Rgba8 { r: 56, g: 183, b: 100, a: 255 })
DEBUG [rx::session] command: PaletteAdd(Rgba8 { r: 37, g: 113, b: 121, a: 255 })
DEBUG [rx::session] command: PaletteAdd(Rgba8 { r: 41, g: 54, b: 111, a: 255 })
DEBUG [rx::session] command: PaletteAdd(Rgba8 { r: 59, g: 93, b: 201, a: 255 })
DEBUG [rx::session] command: PaletteAdd(Rgba8 { r: 65, g: 166, b: 246, a: 255 })
DEBUG [rx::session] command: PaletteAdd(Rgba8 { r: 115, g: 239, b: 247, a: 255 })
DEBUG [rx::session] command: PaletteAdd(Rgba8 { r: 244, g: 244, b: 244, a: 255 })
DEBUG [rx::session] command: PaletteAdd(Rgba8 { r: 148, g: 176, b: 194, a: 255 })
DEBUG [rx::session] command: PaletteAdd(Rgba8 { r: 86, g: 108, b: 134, a: 255 })
DEBUG [rx::session] command: PaletteAdd(Rgba8 { r: 51, g: 60, b: 87, a: 255 })
DEBUG [rx::session] source: /home/lonami/Documents/Rust/rx/.rxrc
DEBUG [rx::session] rx v0.4.0
DEBUG [rx] event: Moved(LogicalPosition { x: 83.0, y: 29.0 })
DEBUG [rx] event: CursorEntered
DEBUG [rx] event: CursorMoved { position: LogicalPosition { x: 742.0, y: 385.0 } }
DEBUG [rx] event: Focused(true)
DEBUG [rx] event: KeyboardInput(KeyboardInput { state: Pressed, key: Some(Shift), modifiers: ModifiersState { shift: false, ctrl: false, alt: false, meta: false } })
DEBUG [rx::session] command: BrushSet(Multi)
DEBUG [rx] event: KeyboardInput(KeyboardInput { state: Pressed, key: Some(Period), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } })
DEBUG [rx] event: ReceivedCharacter(':')
DEBUG [rx] event: KeyboardInput(KeyboardInput { state: Released, key: Some(Period), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } })
DEBUG [rx] event: KeyboardInput(KeyboardInput { state: Released, key: Some(Shift), modifiers: ModifiersState { shift: true, ctrl: false, alt: false, meta: false } })
DEBUG [rx::session] command: BrushUnset(Multi)
DEBUG [rx] event: KeyboardInput(KeyboardInput { state: Pressed, key: Some(Alt), modifiers: ModifiersState { shift: false, ctrl: false, alt: false, meta: false } })
DEBUG [rx] event: CloseRequested
DEBUG [rx::session] state: Running -> Closing(Normal)
```

</details>

Fix patch:
diff --git a/config/init.rx b/config/init.rx
index 6d9f9e99..5dfb933c 100644
--- a/config/init.rx
+++ b/config/init.rx
@@ -7,7 +7,9 @@
 ----------- ------------- -------------------------------------------- -----------------------------------
 --           KEY           COMMAND                                       DESCRIPTION
 ----------- ------------- -------------------------------------------- -----------------------------------
-map         ;             :mode command                             --  Switch to command mode
+map        '?'            :help                                     --  Show help
+map/help   '?'            :help                                     --  Hide help
+map        ':'            :mode command                             --  Switch to command mode
 map         v             :mode visual                              --  Switch to visual mode
 map         .             :zoom +                                   --  Zoom in active view
 map         ,             :zoom -                                   --  Zoom out active view
@@ -34,8 +36,8 @@ map         <left>        :pan -1  0                                --  Pan view
 map         <right>       :pan  1  0                                --  Pan view right
 map         <return>      :f/add                                    --  Add a frame to the view
 map         <backspace>   :f/remove                                 --  Remove a frame from the view
-map/normal  h             :f/prev                                   -- Navigate to previous frame
-map/normal  l             :f/next                                   -- Navigate to next frame
+map/normal  h             :f/prev                                   --  Navigate to previous frame
+map/normal  l             :f/next                                   --  Navigate to next frame
 
 map/visual  j             :selection/move    0 -1
 map/visual  k             :selection/move    0  1
diff --git a/src/cmd.rs b/src/cmd.rs
index 7f3c5e5c..ee7f3bc5 100644
--- a/src/cmd.rs
+++ b/src/cmd.rs
@@ -3,7 +3,7 @@ use crate::brush::{Brush, BrushMode};
 use crate::history::History;
 use crate::parser::*;
 use crate::platform;
-use crate::session::{Direction, Mode, PanState, Tool, VisualState};
+use crate::session::{Direction, Input, Mode, PanState, Tool, VisualState};
 use crate::view::layer::LayerId;
 
 use memoir::traits::Parse;
@@ -162,7 +162,8 @@ impl fmt::Display for Command {
             Self::ForceQuitAll => write!(f, "Quit all views without saving"),
             Self::Map(_) => write!(f, "Map a key combination to a command"),
             Self::MapClear => write!(f, "Clear all key mappings"),
-            Self::Mode(m) => write!(f, "Switch session mode to {}", m),
+            Self::Mode(Mode::Help) => write!(f, "Toggle help"),
+            Self::Mode(m) => write!(f, "Switch to {} mode", m),
             Self::FrameAdd => write!(f, "Add a blank frame to the view"),
             Self::FrameClone(i) => write!(f, "Clone frame {} and add it to the view", i),
             Self::FrameRemove => write!(f, "Remove the last frame of the view"),
@@ -279,7 +280,7 @@ impl From<Command> for String {
 
 #[derive(PartialEq, Debug, Clone)]
 pub struct KeyMapping {
-    pub key: platform::Key,
+    pub input: Input,
     pub press: Command,
     pub release: Option<Command>,
     pub modes: Vec<Mode>,
@@ -313,17 +314,27 @@ impl KeyMapping {
             "<cmd>",
         );
 
-        param::<platform::Key>()
+        let character = between('\'', '\'', character())
+            .map(|c| Input::Character(c))
+            .skip(whitespace())
+            .then(press.clone())
+            .map(|(input, press)| ((input, press), None));
+        let key = param::<platform::Key>()
+            .map(|k| Input::Key(k))
             .skip(whitespace())
             .then(press)
             .skip(optional(whitespace()))
-            .then(optional(between('{', '}', release).label("{<cmd>}")))
-            .map(move |((key, press), release)| KeyMapping {
-                key,
+            .then(optional(between('{', '}', release)));
+
+        character
+            .or(key)
+            .map(move |((input, press), release)| KeyMapping {
+                input,
                 press,
                 release,
                 modes: modes.clone(),
             })
+            .label("<key> <cmd>") // TODO: We should provide the full command somehow.
     }
 }
 
diff --git a/src/event.rs b/src/event.rs
index b4096622..5b31cd89 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -67,7 +67,7 @@ pub enum Event {
     MouseWheel(platform::LogicalDelta),
     CursorMoved(platform::LogicalPosition),
     KeyboardInput(platform::KeyboardInput),
-    ReceivedCharacter(char),
+    ReceivedCharacter(char, platform::ModifiersState),
     Paste(Option<String>),
 }
 
@@ -89,7 +89,7 @@ impl From<Event> for String {
                 };
                 format!("keyboard/input {} {}", key.unwrap(), state)
             }
-            Event::ReceivedCharacter(c) => format!("char/received '{}'", c),
+            Event::ReceivedCharacter(c, _) => format!("char/received '{}'", c),
             Event::Paste(Some(s)) => format!("paste '{}'", s),
             Event::Paste(None) => format!("paste ''"),
         }
@@ -151,7 +151,7 @@ impl FromStr for Event {
                     .followed_by(end())
                     .parse(p)
                     .map_err(|(e, _)| e)?;
-                Ok((Event::ReceivedCharacter(c), p))
+                Ok((Event::ReceivedCharacter(c, Default::default()), p))
             }
             event => Err(parser::Error::new(format!(
                 "unrecognized event {:?}",
diff --git a/src/lib.rs b/src/lib.rs
index b86157a8..022e9fed 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -328,8 +328,8 @@ pub fn init<'a, P: AsRef<Path>>(paths: &[P], options: Options<'a>) -> std::io::R
                     }
                     _ => session_events.push(Event::KeyboardInput(input)),
                 },
-                WindowEvent::ReceivedCharacter(c) => {
-                    session_events.push(Event::ReceivedCharacter(c));
+                WindowEvent::ReceivedCharacter(c, mods) => {
+                    session_events.push(Event::ReceivedCharacter(c, mods));
                 }
                 _ => {}
             };
diff --git a/src/platform/glfw.rs b/src/platform/glfw.rs
index 34d228b3..45e43f3d 100644
--- a/src/platform/glfw.rs
+++ b/src/platform/glfw.rs
@@ -197,7 +197,7 @@ impl From<glfw::WindowEvent> for WindowEvent {
             Glfw::CursorPos(x, y) => WindowEvent::CursorMoved {
                 position: LogicalPosition::new(x, y),
             },
-            Glfw::Char(c) => WindowEvent::ReceivedCharacter(c),
+            Glfw::CharModifiers(c, mods) => WindowEvent::ReceivedCharacter(c, mods.into()),
             Glfw::Key(key, _, action, modifiers) => WindowEvent::KeyboardInput(KeyboardInput {
                 key: Some(key.into()),
                 state: action.into(),
diff --git a/src/platform/mod.rs b/src/platform/mod.rs
index 0f0a4684..8910558e 100644
--- a/src/platform/mod.rs
+++ b/src/platform/mod.rs
@@ -56,7 +56,7 @@ pub enum WindowEvent {
     Destroyed,
 
     /// The window received a unicode character.
-    ReceivedCharacter(char),
+    ReceivedCharacter(char, ModifiersState),
 
     /// The window gained or lost focus.
     Focused(bool),
@@ -110,7 +110,7 @@ impl WindowEvent {
             | Self::Restored
             | Self::CloseRequested
             | Self::Destroyed
-            | Self::ReceivedCharacter(_)
+            | Self::ReceivedCharacter(_, _)
             | Self::Focused(_)
             | Self::KeyboardInput(_)
             | Self::CursorMoved { .. }
diff --git a/src/session.rs b/src/session.rs
index d497af1c..cffc2bc9 100644
--- a/src/session.rs
+++ b/src/session.rs
@@ -440,6 +440,21 @@ impl MessageType {
 /// A session error.
 type Error = String;
 
+#[derive(PartialEq, Copy, Clone, Debug)]
+pub enum Input {
+    Key(Key),
+    Character(char),
+}
+
+impl fmt::Display for Input {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::Key(k) => write!(f, "{}", k),
+            Self::Character(c) => write!(f, "{}", c),
+        }
+    }
+}
+
 /// A key binding.
 #[derive(PartialEq, Clone, Debug)]
 pub struct KeyBinding {
@@ -447,8 +462,8 @@ pub struct KeyBinding {
     pub modes: Vec<Mode>,
     /// Modifiers which must be held.
     pub modifiers: ModifiersState,
-    /// Key which must be pressed or released.
-    pub key: Key,
+    /// Input expected to trigger the binding.
+    pub input: Input,
     /// Whether the key should be pressed or released.
     pub state: InputState,
     /// The `Command` to run when this binding is triggered.
@@ -461,11 +476,33 @@ pub struct KeyBinding {
 }
 
 impl KeyBinding {
-    fn is_match(&self, key: Key, state: InputState, modifiers: ModifiersState, mode: Mode) -> bool {
-        self.key == key
-            && self.state == state
-            && self.modes.contains(&mode)
-            && (self.modifiers == modifiers || state == InputState::Released || key.is_modifier())
+    fn is_match(
+        &self,
+        input: Input,
+        state: InputState,
+        modifiers: ModifiersState,
+        mode: Mode,
+    ) -> bool {
+        match (input, self.input) {
+            (Input::Key(key), Input::Key(k)) => {
+                key == k
+                    && self.state == state
+                    && self.modes.contains(&mode)
+                    && (self.modifiers == modifiers
+                        || state == InputState::Released
+                        || key.is_modifier())
+            }
+            (Input::Character(a), Input::Character(b)) => {
+                // Nb. We only check the <ctrl> modifier with characters,
+                // because the others (especially <shift>) will most likely
+                // input a different character.
+                a == b
+                    && self.modes.contains(&mode)
+                    && self.state == state
+                    && self.modifiers.ctrl == modifiers.ctrl
+            }
+            _ => false,
+        }
     }
 }
 
@@ -477,54 +514,7 @@ pub struct KeyBindings {
 
 impl Default for KeyBindings {
     fn default() -> Self {
-        // The only defaults are switching to command mode and 'help'. On some platforms,
-        // Pressing `<shift> + ;` sends us a `:` directly, while on others
-        // we get `<shift>` and `;`.
-        KeyBindings {
-            elems: vec![
-                KeyBinding {
-                    modes: vec![Mode::Normal, Mode::Help],
-                    modifiers: ModifiersState {
-                        shift: true,
-                        ctrl: false,
-                        alt: false,
-                        meta: false,
-                    },
-                    key: platform::Key::Slash,
-                    state: InputState::Pressed,
-                    command: Command::Mode(Mode::Help),
-                    is_toggle: false,
-                    display: Some("?".to_string()),
-                },
-                KeyBinding {
-                    modes: vec![
-                        Mode::Normal,
-                        Mode::Visual(VisualState::Pasting),
-                        Mode::Visual(VisualState::selecting()),
-                    ],
-                    modifiers: ModifiersState {
-                        shift: true,
-                        ctrl: false,
-                        alt: false,
-                        meta: false,
-                    },
-                    key: platform::Key::Semicolon,
-                    state: InputState::Pressed,
-                    command: Command::Mode(Mode::Command),
-                    is_toggle: false,
-                    display: Some(":".to_string()),
-                },
-                KeyBinding {
-                    modes: vec![Mode::Normal],
-                    modifiers: ModifiersState::default(),
-                    key: platform::Key::Colon,
-                    state: InputState::Pressed,
-                    command: Command::Mode(Mode::Command),
-                    is_toggle: false,
-                    display: None,
-                },
-            ],
-        }
+        KeyBindings { elems: vec![] }
     }
 }
 
@@ -538,7 +528,7 @@ impl KeyBindings {
     pub fn add(&mut self, binding: KeyBinding) {
         for mode in binding.modes.iter() {
             self.elems
-                .retain(|kb| !kb.is_match(binding.key, binding.state, binding.modifiers, *mode));
+                .retain(|kb| !kb.is_match(binding.input, binding.state, binding.modifiers, *mode));
         }
         self.elems.push(binding);
     }
@@ -554,7 +544,7 @@ impl KeyBindings {
     /// Find a key binding based on some input state.
     pub fn find(
         &self,
-        key: Key,
+        input: Input,
         modifiers: ModifiersState,
         state: InputState,
         mode: Mode,
@@ -563,7 +553,7 @@ impl KeyBindings {
             .iter()
             .rev()
             .cloned()
-            .find(|kb| kb.is_match(key, state, modifiers, mode))
+            .find(|kb| kb.is_match(input, state, modifiers, mode))
     }
 
     /// Iterate over all key bindings.
@@ -935,6 +925,9 @@ impl Session {
                 v.update(delta);
             }
         }
+        if self.ignore_received_characters {
+            self.ignore_received_characters = false;
+        }
 
         let exec = &mut *exec.borrow_mut();
 
@@ -2133,7 +2126,7 @@ impl Session {
                 }
             }
             Event::KeyboardInput(input) => self.handle_keyboard_input(input, exec),
-            Event::ReceivedCharacter(c) => self.handle_received_character(c),
+            Event::ReceivedCharacter(c, mods) => self.handle_received_character(c, mods),
             Event::Paste(p) => self.handle_paste(p),
         }
     }
@@ -2379,16 +2372,17 @@ impl Session {
         }
     }
 
-    fn handle_received_character(&mut self, c: char) {
+    fn handle_received_character(&mut self, c: char, mods: ModifiersState) {
         if self.mode == Mode::Command {
-            if c.is_control() {
-                return;
-            }
-            if self.ignore_received_characters {
-                self.ignore_received_characters = false;
+            if c.is_control() || self.ignore_received_characters {
                 return;
             }
             self.cmdline_handle_input(c);
+        } else if let Some(kb) =
+            self.key_bindings
+                .find(Input::Character(c), mods, InputState::Pressed, self.mode)
+        {
+            self.command(kb.command);
         }
     }
 
@@ -2471,7 +2465,10 @@ impl Session {
                 _ => {}
             }
 
-            if let Some(kb) = self.key_bindings.find(key, modifiers, state, self.mode) {
+            if let Some(kb) = self
+                .key_bindings
+                .find(Input::Key(key), modifiers, state, self.mode)
+            {
                 // For toggle-like key bindings, we don't want to run the command
                 // on key repeats. For regular key bindings, we run the command
                 // depending on if it's supposed to repeat.
@@ -3092,24 +3089,24 @@ impl Session {
             }
             Command::Map(map) => {
                 let KeyMapping {
-                    key,
+                    input,
                     press,
                     release,
                     modes,
                 } = *map;
 
                 self.key_bindings.add(KeyBinding {
-                    key,
+                    input,
                     modes: modes.clone(),
                     command: press,
                     state: InputState::Pressed,
                     modifiers: platform::ModifiersState::default(),
                     is_toggle: release.is_some(),
-                    display: Some(format!("{}", key)),
+                    display: Some(format!("{}", input)),
                 });
                 if let Some(cmd) = release {
                     self.key_bindings.add(KeyBinding {
-                        key,
+                        input,
                         modes,
                         command: cmd,
                         state: InputState::Released,
@@ -3393,7 +3390,7 @@ mod test {
 
         let kb1 = KeyBinding {
             modes: vec![Mode::Normal],
-            key: platform::Key::A,
+            input: Input::Key(platform::Key::A),
             command: Command::Noop,
             is_toggle: false,
             display: None,
@@ -3422,7 +3419,7 @@ mod test {
 
         assert_eq!(kbs.len(), 2, "bindings can be overwritten");
         assert_eq!(
-            kbs.find(kb2.key, kb2.modifiers, kb2.state, kb2.modes[0]),
+            kbs.find(kb2.input, kb2.modifiers, kb2.state, kb2.modes[0]),
             Some(kb3),
             "bindings can be overwritten"
         );
@@ -3432,7 +3429,7 @@ mod test {
     fn test_key_bindings_modifier() {
         let kb = KeyBinding {
             modes: vec![Mode::Normal],
-            key: platform::Key::Control,
+            input: Input::Key(platform::Key::Control),
             command: Command::Noop,
             is_toggle: false,
             display: None,
@@ -3445,7 +3442,7 @@ mod test {
 
         assert_eq!(
             kbs.find(
-                platform::Key::Control,
+                Input::Key(platform::Key::Control),
                 ModifiersState {
                     ctrl: true,
                     alt: false,
@@ -3460,7 +3457,7 @@ mod test {
 
         assert_eq!(
             kbs.find(
-                platform::Key::Control,
+                Input::Key(platform::Key::Control),
                 ModifiersState::default(),
                 InputState::Pressed,
                 Mode::Normal


Test patch: