# About KeyCode, KeySym, Modifiers, Crossplatform and reliable code

**URL:** <https://discourse.vtk.org/t/about-keycode-keysym-modifiers-crossplatform-and-reliable-code/12598>\
**Category:** Development\
**Created:** [November 1, 2023, 3:27pm UTC](https://discourse.vtk.org/t/about-keycode-keysym-modifiers-crossplatform-and-reliable-code/12598 "2023-11-01T15:27:04Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![mwestphal](https://discourse.vtk.org/user_avatar/discourse.vtk.org/mwestphal/32/19_2.png) [@mwestphal](https://discourse.vtk.org/u/mwestphal)\
**Post date:** [November 1, 2023, 3:27pm UTC](https://discourse.vtk.org/t/about-keycode-keysym-modifiers-crossplatform-and-reliable-code/12598/1 "2023-11-01T15:27:04Z")

</div>

A few months ago I found a very small issue with the XRenderWindowInteractor where I could not trigger `?` keysym on my program.

This led me to a rabbit hole and an issue that I’m currently fixing here: [https://gitlab.kitware.com/vtk/vtk/-/merge\_requests/10652](https://gitlab.kitware.com/vtk/vtk/-/merge_requests/10652)?

However, in the process, I’ve leaned a lot about how key interactions are handled and especially how VTK is limited here. I of course am talking about pure VTK, one can always rely on another framework like Qt to handle key interaction.

First some vocabulary.

- Virtual key: A OS dependant value corresponding to a physical key on the keyboard. Not impacted by keyboard layout, not impacted by modifiers but non consistent accross OS.
- KeyCode: A single (unsigned, but read below) char on the extended ASCII table corresponding to a Key being pressed, impacted by BOTH modifiers and layout.
- KeySym: A string representation of a key being pressed, eg `delete` or `BaskSpace` but can be a single char for letters. Impacted by both modifiers and layout.
- KeyCode and KeySym are inspired by Xorg API, but KeyCode is a cross platform concept (although under different names). KeySym doesn’t really exist accross OS but VTK tries to figure it out anyway.

Here is how it currently works.

1. A physical key is pressed and the OS receive it, send the information to VTK with its Virtual Key.
2. VTK receive the event and the Virtual Key
3. VTK ask the OS to convert the Virtual Key to a KeyCode, then uses different methods to produce a KeySym from the KeyCode

One would say it is all well and good, however, I conducted some testing, here are the results (QWERTY Keyboard):

> **[keyCode\_keySym](https://docs.google.com/spreadsheets/d/1egKfR9-WwKDfF29f7ZH_jmendGtk7wUGENgYIHdTmhs/edit?usp=sharing)**
>
> Sheet1
> 
> XOrg,Win32,Cocoa
> vkey,keycode,keysym,vkey,keycode,keysym,vkey,keycode,keysym
> A,38,97,a,65,97,a,0,97,a
> Shift + A,38,65,A,65,65,A,0,65,A
> Control + A,38,1,a,65,1,a,0,1,nullptr
> Alt + A,38,97,a,65,97,a,0,0,nullptr
> 1!,10,49,1,49,49,1,18,49,1
> Shift...

 ![a](https://discourse.vtk.org/uploads/default/original/2X/3/39eadbdca1083e666651b0eb7a4b51e1215af3ee.png)

1. `Control` modifier

The first striking thing is that `Control` modifier has impact on the KeyCode, which could have been unexpected until you learn about, well, `Control Codes`: [Control codes - converting to ASCII, hex or decimal](https://www.windmill.co.uk/ascii-control-codes.html)

Indeed, control codes is a cross platfrom ancient behavior, but they do impact the KeyCode.

But then we can see the outside of control codes (eg: Ctrl + 1), Win32 behavior is different.

Here is the most important question of this post:

**As a VTK user, do you expect `GetKeyCode` to provide Control codes or not ?**

In any case, we definitely need to add a `GetKeyCodeWithouModifiersExceptShift` method somewhere. Turns out MacOS already thought of that and provide it in its API, so I think either modifying GetKeyCode in that direction or adding a new method makes complete sense.

We also need to ensure keycode consistency across OS when pressing Control.

Without it, we can ONLY rely on keysym.

1. KeyCode consistency

Outside of the `Control` modifier, we can see that certain key do not behave consistently in regards to KeyCode, this should be fixed. to be fixed.

1. KeySym consistency

For certain characters, KeySym is not consistent, this is because outside of XOrg, the KeyCode → KeySym mapping is done in tables inside VTK and these tables are just not complete, this should be fixed. to be fixed.

1. MacOS

MacOS has multiple issues, for starters `Alt` modifier is completely broken. Moreover, it should default KeySym to `None` instead of nullptr. to be fixed.

1. Extended ASCII

Outside of QWERTY there are other keyboard layout wich have keys in the extended ASCII table. It works surprisingly well for the keycode (Win32 and Xorg) however GetKeyCode API uses `char`, not `unsigned char` as it should. Issue to be open.

1. vtkCallBackMapper:: SetCallbackMethod

[https://vtk.org/doc/nightly/html/classvtkWidgetCallbackMapper.html#a5f7a00eecce9a946fe9dc8adfc41e9c4](https://vtk.org/doc/nightly/html/classvtkWidgetCallbackMapper.html#a5f7a00eecce9a946fe9dc8adfc41e9c4)

This API is very confusing:

```auto
void vtkWidgetCallbackMapper::SetCallbackMethod ( unsigned long VTKEvent,
		int modifiers,
		char keyCode,
		int repeatCount,
		const char * keySym,
		unsigned long widgetEvent,
		vtkAbstractWidget * w,
		CallbackType f 
	) 	

```

Because it support `vtkEvent::AnyModifiers`, yes the keyCode and the keySym and impacted by the modifier, so if a developer want a callback to be called whatever the modifier, the following is required:

```auto
  this->CallbackMapper->SetCallbackMethod(vtkCommand::KeyPressEvent, vtkEvent::AnyModifier, 'p', 1,
    "p", vtkWidgetEvent::PickPoint, this, vtkDisplaySizedImplicitPlaneWidget::PickOriginAction);
  this->CallbackMapper->SetCallbackMethod(vtkCommand::KeyPressEvent, vtkEvent::AnyModifier, 'P', 1,
    "P", vtkWidgetEvent::PickPoint, this, vtkDisplaySizedImplicitPlaneWidget::PickOriginAction);
  this->CallbackMapper->SetCallbackMethod(vtkCommand::KeyPressEvent, vtkEvent::AnyModifier, 16, 1,
    "p", vtkWidgetEvent::PickPoint, this, vtkDisplaySizedImplicitPlaneWidget::PickOriginAction);

```

Very unpractical. One could imagine a better API where only a case agnostic KeySym is provided.  
Issue to be open.

**TLDR:**

`char vtkRenderWindowInteractor::GetKeyCode`  
does not behave as developpers expect, should we change its behavior to behave as expected or provide new, clearer API to do so, eg:  
`unsigned char vtkRenderWindowInteractor::GetKeyCodeWithoutModifiers()` and `unsigned char vtkRenderWindowInteractor::GetKeyCodeWithoutModifiersExceptShift()`

---

<div class="post-metadata">

**Author:** ![Jens\_Munk\_Hansen](https://discourse.vtk.org/user_avatar/discourse.vtk.org/jens_munk_hansen/32/1215_2.png) [@Jens\_Munk\_Hansen](https://discourse.vtk.org/u/Jens_Munk_Hansen)\
**Post date:** [November 1, 2023, 8:01pm UTC](https://discourse.vtk.org/t/about-keycode-keysym-modifiers-crossplatform-and-reliable-code/12598/2 "2023-11-01T20:01:36Z")

</div>

That is really nice that you pick this up. I have been using the `vtkWidgetCallbackMapper` in C++, which I find quite confusing. I often prefer to use `VTK` without `Qt` and even developed my own small library for executing a call in the main thread since I need an alternative to `QueuedConnection`. In Python, I sometimes ask for the ‘SHIFT’ modified in a callback and even if I hold down shift I often see a result that the modifier is not enabled. Here it would be much simpler if the keysym contained the modifier as well. Thanks for looking into this

---

<div class="post-metadata">

**Author:** ![dgobbi](https://discourse.vtk.org/user_avatar/discourse.vtk.org/dgobbi/32/18_2.png) [@dgobbi](https://discourse.vtk.org/u/dgobbi)\
**Post date:** [November 1, 2023, 9:22pm UTC](https://discourse.vtk.org/t/about-keycode-keysym-modifiers-crossplatform-and-reliable-code/12598/3 "2023-11-01T21:22:37Z")

</div>

> [@Jens\_Munk\_Hansen](#):
>
> Here it would be much simpler if the keysym contained the modifier as well.

Historically keysym has not contained the modifier, so making that change would break a lot of existing code.

VTK has a Qt-to-keysym translation table in the QVTKInteractorAdapter class, and we can possibly use this as our guide to “correct” behavior. This is what Paraview, Slicer, and other Qt/VTK applications use for setting the keysym in the interactor, and it would be nice if the native X11, Win32, macOS interactors give the same key information as e.g. Paraview’s interactor. But I’m not 100% sure if it handles control modifiers correctly, so it might also need fixes.

---

<div class="post-metadata">

**Author:** ![mwestphal](https://discourse.vtk.org/user_avatar/discourse.vtk.org/mwestphal/32/19_2.png) [@mwestphal](https://discourse.vtk.org/u/mwestphal)\
**Post date:** [November 1, 2023, 10:44pm UTC](https://discourse.vtk.org/t/about-keycode-keysym-modifiers-crossplatform-and-reliable-code/12598/4 "2023-11-01T22:44:11Z")

</div>

Indeed, I agree with @dgobbi here, the KeySym will not contain the modifier. The modifiers are accessible via the dedicated methods.

> it would be nice if the native X11, Win32, macOS interactors give the same key information as e.g. Paraview’s interactor. But I’m not 100% sure if it handles control modifiers correctly, so it might also need fixes.

That is exactlyt what I’m aiming for.

---

<div class="post-metadata">

**Author:** ![mwestphal](https://discourse.vtk.org/user_avatar/discourse.vtk.org/mwestphal/32/19_2.png) [@mwestphal](https://discourse.vtk.org/u/mwestphal)\
**Post date:** [November 2, 2023, 8:49am UTC](https://discourse.vtk.org/t/about-keycode-keysym-modifiers-crossplatform-and-reliable-code/12598/5 "2023-11-02T08:49:08Z")

</div>

Windows inconsistencies are being fixed here:  
[https://gitlab.kitware.com/vtk/vtk/-/merge\_requests/10667](https://gitlab.kitware.com/vtk/vtk/-/merge_requests/10667)

MacOS inconsistencies are being fixed here:  
[https://gitlab.kitware.com/vtk/vtk/-/merge\_requests/10668](https://gitlab.kitware.com/vtk/vtk/-/merge_requests/10668)

---

<div class="post-metadata">

**Author:** ![mwestphal](https://discourse.vtk.org/user_avatar/discourse.vtk.org/mwestphal/32/19_2.png) [@mwestphal](https://discourse.vtk.org/u/mwestphal)\
**Post date:** [November 5, 2023, 10:13am UTC](https://discourse.vtk.org/t/about-keycode-keysym-modifiers-crossplatform-and-reliable-code/12598/6 "2023-11-05T10:13:37Z")

</div>

Just a quick follow up about Xorg implementation. It looks like `Alt Gr` (see ISO layout) does not set the Alt modifier on the RenderWindowInteractor. Not sure if this is the expected. The keysym is still good.

---

<div class="post-metadata">

**Author:** ![mwestphal](https://discourse.vtk.org/user_avatar/discourse.vtk.org/mwestphal/32/19_2.png) [@mwestphal](https://discourse.vtk.org/u/mwestphal)\
**Post date:** [November 5, 2023, 10:42am UTC](https://discourse.vtk.org/t/about-keycode-keysym-modifiers-crossplatform-and-reliable-code/12598/7 "2023-11-05T10:42:47Z")

</div>

Quick follow up the MacOS implementation. Pressing the `Option` key is mapped to the `Alt` modifier, however the macos keyboard layout is not standard and pressing such key provides special KeyCode symbols.

---

<div class="post-metadata">

**Author:** ![mwestphal](https://discourse.vtk.org/user_avatar/discourse.vtk.org/mwestphal/32/19_2.png) [@mwestphal](https://discourse.vtk.org/u/mwestphal)\
**Post date:** [November 5, 2023, 11:29am UTC](https://discourse.vtk.org/t/about-keycode-keysym-modifiers-crossplatform-and-reliable-code/12598/8 "2023-11-05T11:29:39Z")

</div>

I’ve update the document with the last code changes that are soon to be merged, I’ve extended my tests to include azerty layout too.

> **[keyCode\_keySym](https://docs.google.com/spreadsheets/d/1egKfR9-WwKDfF29f7ZH_jmendGtk7wUGENgYIHdTmhs/edit?usp=sharing)**
>
> Original
> 
> XOrg,Win32,Cocoa
> vkey,keycode,keysym,vkey,keycode,keysym,vkey,keycode,keysym
> A,38,97,a,65,97,a,0,97,a
> Shift + A,38,65,A,65,65,A,0,65,A
> Control + A,38,1,a,65,1,a,0,1,nullptr
> Alt +...

 ![a](https://discourse.vtk.org/uploads/default/original/2X/0/06b28a41d93e09b59e1c553bd77eea3aa4f91dc1.png)

First, it is already looking much better, with more consistent keycodes and keysyms accross OSes. I remarked a few more things:

1. Testing and keysyms on Win/MacOS

KeySym on windows and MacOS are not provided by the OS but builtin VTK based on the different OS provided keycodes. This explains why `quoteright` which is not even part of the Latin1 ASCII is listed there. It is a very easy fix, which I will take care of.

However it raises the question of testing. Currently the logic the translate a OS virtual key code into an actual keyCode and keySym is completely untested in VTK. Indeed, VTK testing system interface itself just **after** that.

I will investigate the possiblity of adding simple or even exhaustive testing of this layer.

1. ASCII vs Latin1 support

Xorg implementation always nativaly supported Latin1 ([ISO/IEC 8859-1 - Wikipedia](https://en.wikipedia.org/wiki/ISO/IEC_8859-1)) for KeyCode **and** KeySym, while Win32 supported it for KeyCode only and macOS did not support it at all. I’ve added support for KeyCode in MacOS as it was trivial but in order to support KeySym too, more work is needed.

Indeed, there are big translation tables from KeyCode to KeySym in MacOS and Win32. I will try to complete them but the process may prove hard, especially without testing.

Also, the value of these `>=128` Latin1 keycode are currently negative as the API is using a `char`. **This must change.**

1. Cross platform interaction

While Windows and Linux are quite respectful of the keyboard layout standard, MacOS does what MacOS do with custom keyboard and such, so the modifiers and layouts are slightly different.

Which means that certains combination of keys using `Alt` modifier will just not be available. You won’t be able to get `Alt` modifier with `a` keycode, because `Alt` + `a` on a MacOS is `Å`.

In a way, this is expected and visible on the physical key itself. If the user pressed the same combination of key in a text editor, the will get the `Å` as well!

But it also means that, when coding a cross platform widget in VTK, you cannot expect your interactions to work on all layout and all OSes.

One solution is to provide full configuration of the interaction. While this is usually possible at application level, this is not really an option in VTK Widgets.

This is where the API I suggested above start to make complete sense. For cross platform widgets, one absolutaly needs keycodes without modifiers. It is not bullet proof but way more stable accross layout and OSes. Basically the whole alphabet `[a-z]` would become fully cross-layout and cross-platform.

So I now believe this API addition is indeed required.

---

<div class="post-metadata">

**Author:** ![dgobbi](https://discourse.vtk.org/user_avatar/discourse.vtk.org/dgobbi/32/18_2.png) [@dgobbi](https://discourse.vtk.org/u/dgobbi)\
**Post date:** [November 5, 2023, 4:29pm UTC](https://discourse.vtk.org/t/about-keycode-keysym-modifiers-crossplatform-and-reliable-code/12598/9 "2023-11-05T16:29:58Z")

</div>

It’s good to see the better consistency (and to see that keysym is never `nullptr`).

> [@mwestphal](#):
>
> This explains why `quoteright` which is not even part of the Latin1 ASCII is listed there.

The keysym `quoteright` just means ASCII singe quote, but that name has been deprecated in favor of `apostrophe`. The deprecated keysym name got into VTK’s char-to-keysym translation tables via Tk, which uses the obsolete name. From the [**X11/keysymdef.h** header file](https://opensource.apple.com/source/tcl/tcl-97.1/tk84/tk/xlib/X11/keysymdef.h.auto.html) in the Latin1 section:

```c
#define XK_apostrophe 0x027
#define XK_quoteright 0x027	/* deprecated */

```

```c
#define XK_grave 0x060
#define XK_quoteleft 0x060	/* deprecated */

```

The curly quotes have keysyms `leftsinglequotemark` and `rightsinglequotemark` (but I think no keyboards have these keys).

> [@](#):
>
> Also, the value of these `>=128` Latin1 keycode are currently negative as the API is using a `char`. **This must change.**

This is especially troublesome because `char` is not always signed. Returning any value outside of the range `[0,127]` is platform-dependent. There is also a difficulty when people pass the returned value to `isprint()` or similar… if the code is negative or out-of-range for the locale, then `isprint()` has undefined behavior (on MSVC, I have seen calls to `isprint()` do an abort() and exit if the value is out-of-range).

One real difficulty is that people expect `GetKeyCode()` to do two different things:

1. identify the key that was pressed (identified by the letter/symbol/word printed on the key)
2. return a string containing the text that is generated by the keypress including the effect of modifiers (possibly 0 chars of text or even multiple chars of text)

One single API can’t do both of these things. A proper fix would require `GetKeyCode()` to be split into two APIs. However, this small change would require quite a lot of work.

---

<div class="post-metadata">

**Author:** ![mwestphal](https://discourse.vtk.org/user_avatar/discourse.vtk.org/mwestphal/32/19_2.png) [@mwestphal](https://discourse.vtk.org/u/mwestphal)\
**Post date:** [November 5, 2023, 5:13pm UTC](https://discourse.vtk.org/t/about-keycode-keysym-modifiers-crossplatform-and-reliable-code/12598/10 "2023-11-05T17:13:20Z")

</div>

About `quoteright`:

Good point, for consistency, it still should be updated though.

About `char` and `unsigned char`:

I had no idea, I would expect a char to always be signed ? In any case, this is one more argument to use the correct api. In the meantime, I could improve GetKeyCode doc and provide an example of converting to unsigned char correctly before usage.

About `GetKeyCode`:

I don’t completely agree. I think `GetKeyCode` should take modifiers into account and provide the correct Latin1 ASCII encoded `char` corresponding to the Key pressed with the modifiers. This is how it was implemented and how it should be used. But I’m advocating to add more methods to provided indeed a `GetKeyCodeIgnoringModifiers`. I’m not enterely sure how it should be implemented but It should not be too hard to do.

---

<div class="post-metadata">

**Author:** ![mwestphal](https://discourse.vtk.org/user_avatar/discourse.vtk.org/mwestphal/32/19_2.png) [@mwestphal](https://discourse.vtk.org/u/mwestphal)\
**Post date:** [November 6, 2023, 8:05am UTC](https://discourse.vtk.org/t/about-keycode-keysym-modifiers-crossplatform-and-reliable-code/12598/11 "2023-11-06T08:05:17Z")

</div>

Proper internal Latin1 support being added here: [https://gitlab.kitware.com/vtk/vtk/-/merge\_requests/10680](https://gitlab.kitware.com/vtk/vtk/-/merge_requests/10680)

---

<div class="post-metadata">

**Author:** ![mwestphal](https://discourse.vtk.org/user_avatar/discourse.vtk.org/mwestphal/32/19_2.png) [@mwestphal](https://discourse.vtk.org/u/mwestphal)\
**Post date:** [November 10, 2023, 7:31am UTC](https://discourse.vtk.org/t/about-keycode-keysym-modifiers-crossplatform-and-reliable-code/12598/12 "2023-11-10T07:31:21Z")

</div>

With the latest fix, I’ve updated my spreadsheet:

> **[keyCode\_keySym](https://docs.google.com/spreadsheets/d/1egKfR9-WwKDfF29f7ZH_jmendGtk7wUGENgYIHdTmhs/edit#gid=1768153898)**
>
> Original
> 
> XOrg,Win32,Cocoa
> vkey,keycode,keysym,vkey,keycode,keysym,vkey,keycode,keysym
> A,38,97,a,65,97,a,0,97,a
> Shift + A,38,65,A,65,65,A,0,65,A
> Control + A,38,1,a,65,1,a,0,1,nullptr
> Alt +...

 ![a](https://discourse.vtk.org/uploads/default/original/2X/2/2d9930594465bd1fb6327c16eac5351453ece78e.png)

None of the remaining difference can be considered bugs. This close my (initial) work on this.  
The odd behavior on MacOS with alt is expected, is that is the key symbol that is supposed to be emitted.

**Conclusions**

Working with `Alt` modifier is not recommended, depending on the OS in use, it will behave wildly different.

Working with `GetKeyCode` and modifiers is not recommended at all. It will not behave as you expect it.

Working with `GetKeySym` and modifiers work (outside of `Alt`) as long as one understands that `Shift` modifier may have an impact of the `KeySym`.

In the context of alphas (letters), using `ToUpper` on the `KeySym` with `Shift` and `Control` modfiers will work cross-platforms and cross-layout.

`Control` modifier works with almost all `KeySym`, on all layout and all OSes, but there are a few exceptions.

**Future work**

There are two main future work.

1. Deprecation for `char GetKeyCode()` in favor of `unsigned char GetKeyCode()`
2. Addition of `unsigned char GetKeyCodeWithoutModifiers()`.
3. Improve testing framework to test platform-specific implementation.

At this point, `GetKeySym` is enough for my needs, but if the need arise, I hope someone will implement these!

---

<div class="post-metadata">

**Author:** ![dgobbi](https://discourse.vtk.org/user_avatar/discourse.vtk.org/dgobbi/32/18_2.png) [@dgobbi](https://discourse.vtk.org/u/dgobbi)\
**Post date:** [November 10, 2023, 3:48pm UTC](https://discourse.vtk.org/t/about-keycode-keysym-modifiers-crossplatform-and-reliable-code/12598/13 "2023-11-10T15:48:50Z")

</div>

Thanks for the work that you have put into this, Mathieu. For future work, I would prefer to move away from using either `char` or `unsigned char`, since 256 codes is just not enough. Instead, I would like to get the actual text (as utf-8) generated by the key press:

1. `std::string GetKeyText()`
2. `std::string GetKeyTextWithoutModifiers()`

Unlike `GetKeyCode()`, this can easily be extended to all international keyboards and text input methods, without the need of large conversion tables inside VTK.

Hence, people could use `GetKeySym()` to check for special keys like ‘Up’, ‘Down’, ‘Delete’, but they would use `GetKeyText()` for ordinary keystrokes like ‘A’, ‘a’, ‘?’, or any unicode characters generated by international keyboards.

---

<div class="post-metadata">

**Author:** ![mwestphal](https://discourse.vtk.org/user_avatar/discourse.vtk.org/mwestphal/32/19_2.png) [@mwestphal](https://discourse.vtk.org/u/mwestphal)\
**Post date:** [December 7, 2024, 6:20am UTC](https://discourse.vtk.org/t/about-keycode-keysym-modifiers-crossplatform-and-reliable-code/12598/14 "2024-12-07T06:20:20Z")

</div>

Found a small issue with `BackSpace` on macOS and fixed it here: [https://gitlab.kitware.com/vtk/vtk/-/merge\_requests/11738](https://gitlab.kitware.com/vtk/vtk/-/merge_requests/11738)
