Repository: beeware/toga

Issue:
Missing GDK key definitions
### Describe the bug

Several keys are throwing the error below:
```
Traceback (most recent call last):
  File "/home/russell/.pyenv/versions/briefcase-3.10/lib/python3.10/site-packages/toga_gtk/widgets/textinput.py", line 29, in gtk_key_press_event
    key_pressed = toga_key(user_data)
  File "/home/russell/.pyenv/versions/briefcase-3.10/lib/python3.10/site-packages/toga_gtk/keys.py", line 228, in toga_key
    key = GDK_KEYS[event.keyval]
KeyError: 65436
```

Affected keys:
left shift: 0xFFE1
left ctrl: 0xFFE3
left alt: 0xFFE9
right shift: 0xFFE2
right alt: 0xFFEA
right super: 0xFFEC
right context: 0xFF67
right ctrl: 0xFFE4
insert: 0xFF63
...as well as the entire numpad (whether or not numlock is enabled)

### Steps to reproduce

Use the number pad in a `TextInput` using GTK.

### Expected behavior

The keys are mapped correctly.

### Screenshots

_No response_

### Environment

- Operating System: pop os 22.04
- Python version: 3.10.13
- Software versions:
  - Briefcase: `0.3.17.dev54+g39040475.d20231112`
  - Toga: `0.4.1.dev19+g69fc00cb5`

### Logs

[briefcase.2023_11_11-20_21_13.dev.log](https://github.com/beeware/toga/files/13326905/briefcase.2023_11_11-20_21_13.dev.log)


### Additional context

_No response_


Discussion:
can i get a screenshot of your code please to know which code giving this error exactly.  
> can i get a screenshot of your code please to know which code giving this error exactly.

Sure; this'll cause the error.

```python
import toga


class HelloWorld(toga.App):

    def startup(self):
        """Construct and show the Toga application."""
        main_box = toga.Box()
        main_box.add(toga.TextInput())

        self.main_window = toga.MainWindow(title=self.formal_name)
        self.main_window.content = main_box
        self.main_window.show()


def main():
    return HelloWorld()
```
A technical question, has this issue actually been completed in its entirety? The PR seems from the description to be focused on numpad keys, I can't test those as I don't have a physical num-block, but combinations involving Ctrl, Shift and Alt on TextInput still produce errors for me. Try pressing things like Shift+Tab, Ctrl+Tab, Alt+F4, alt+Tab, output is the same as the original bug report.

Thanks!
@RastislavKish There's also a bunch of key improvements in #2415, so the fixes aren't entirely part of #2232. 

The issue reported by this ticket appears to be resolved in my testing (i.e., numpad keys, plus the use of modifiers); I definitely don't see the issue related to this specific ticket (i.e., seeing a KeyError) with Tab keys.

However, if I missed some keys in my testing, or if there are key combinations that aren't working, then feel free to open a specific bug report with the keys/combos that aren't working.

Regarding Tab specifically - that's going to be a complex one, because CMD-tab/ALT-tab are often OS-level commands for task or app switching, so it might not be possible to support those in a generic way. The same goes for *some* function keys. At the very least, it's probably worth flagging this in the docs for commands.


@freakboy3742 the keyboard combinations are in-fact working functionally, i.e. pressing Shift+Tab moves the cursor, Alt+F4 closes the focused window etc. it's just that errors are thrown in the terminal, even if from the user's perspective, everything works as expected. Which distro are you using for testing purposes? I can see the original post reported pop os, maybe it's some Ubuntu GTK packaging oddity, I can try to reproduce this elsewhere and open a separate issue then.
I do most of my Linux testing on Ubuntu 22.04 LTS. 
I'm seeing the same thing. The keys are functioning but throw an error on the terminal. 
The following keys cause the errors:
- **insert**: KeyError: 65379
- **caps lock**: KeyError: 65379
- **shift**: KeyError: 65379
- **ctrl**: KeyError: 65379
- **alt**: KeyError: 65379
- **number-pad**: KeyError: 65450, 65451, 65453, 65454, 65455, 65456, 65457, 65458, 65459, 65460, 65461, 65462, 65463, 65464, 65465

I noticed the errors while working through the BeeWare Tutorial 2.
I using Ubuntu 22.04.4 LTS 



@28Candler62 Are you seeing this on the main branch of Toga, or the most recent 0.4.2 release? The changes from #2232 have been merged, but aren't in a published release at this time.
@freakboy3742 
toga-core             0.4.2
toga-gtk              0.4.2
Same issue. KeyError: 65505

@jbesq-ml ... and the same question: Are you running Toga's main branch, or Toga 0.4.2? If you're running 0.4.2, that version does not include the fix for this problem.
To confirm, there are still missing keys on `main`.

65505 - L SHIFT
65506 - R SHIFT
65407 - NUMLOCK
65056 - LEFT TAB (SHIFT + TAB)
65379 - INSERT

as a few examples.

Toga version: `0.4.3.dev576+g75d62016c`

Full list perhaps: https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gdk/gdkkeysyms.h
SHIFT and RSHIFT *shouldn't* be generating keystrokes at all - shift should be treated as a modifier. I've tried running a GTK app, and I don't get any warnings for SHIFT keypresses; how are you getting a keycode generated for a SHIFT?

LEFT TAB *should* be caught as a modifier; if it's not being caught by "SHIFT + TAB", then clearly it needs special handling. In my testing. TAB and LEFT TAB are both bound to focus changes, so they're probably in the territory of "thing that should be platform-prohibited (or at least warned) as key bindings.

The other two are missing as a consequence of using an Apple keyboard that doesn't have those two keys :-)

Fix incoming shortly.
> SHIFT and RSHIFT _shouldn't_ be generating keystrokes at all - shift should be treated as a modifier. I've tried running a GTK app, and I don't get any warnings for SHIFT keypresses; how are you getting a keycode generated for a SHIFT?

I'm not sure....but basically any way I start the app, I can trigger the warnings. Furthermore, though, it happens for left and right CTRL, ALT, and SUPER.

Some other missing keys i just found:
- `GDK_KEY_KP_Begin 0xff9d` (65437) (num pad 5 with numlock off)
- `GDK_KEY_KP_Insert 0xff9e` (65438) (num pad 0 with numlock off)
- `GDK_KEY_Pause 0xff13` (65299)
- `GDK_KEY_Scroll_Lock 0xff14` (65300)
- `GDK_KEY_Menu 0xff67` (65383)

Resolving patch:
diff --git a/core/src/toga/keys.py b/core/src/toga/keys.py
--- a/core/src/toga/keys.py
+++ b/core/src/toga/keys.py
@@ -119,6 +119,7 @@ class Key(Enum):
 
     HOME = "<home>"
     END = "<end>"
+    INSERT = "<insert>"
     DELETE = "<delete>"
     PAGE_UP = "<pg up>"
     PAGE_DOWN = "<pg dn>"
@@ -128,6 +129,7 @@ class Key(Enum):
     LEFT = "<left>"
     RIGHT = "<right>"
 
+    NUMLOCK = "<num lock>"
     NUMPAD_0 = "numpad:0"
     NUMPAD_1 = "numpad:1"
     NUMPAD_2 = "numpad:2"
@@ -147,6 +149,11 @@ class Key(Enum):
     NUMPAD_MULTIPLY = "numpad:*"
     NUMPAD_PLUS = "numpad:+"
 
+    SCROLLLOCK = "<scroll lock>"
+    BEGIN = "<begin>"
+    MENU = "<menu>"
+    PAUSE = "<pause>"
+
     def is_printable(self) -> bool:
         """Does pressing the key result in a printable character?"""
         return not (self.value.startswith("<") and self.value.endswith(">"))
diff --git a/gtk/src/toga_gtk/keys.py b/gtk/src/toga_gtk/keys.py
--- a/gtk/src/toga_gtk/keys.py
+++ b/gtk/src/toga_gtk/keys.py
@@ -102,6 +102,7 @@
     Gdk.KEY_X: Key.X,
     Gdk.KEY_Y: Key.Y,
     Gdk.KEY_Z: Key.Z,
+    Gdk.KEY_Caps_Lock: Key.CAPSLOCK,
     Gdk.KEY_Tab: Key.TAB,
     Gdk.KEY_bracketleft: Key.OPEN_BRACKET,
     Gdk.KEY_bracketright: Key.CLOSE_BRACKET,
@@ -121,7 +122,10 @@
     Gdk.KEY_less: Key.LESS_THAN,
     Gdk.KEY_greater: Key.GREATER_THAN,
     Gdk.KEY_question: Key.QUESTION,
+    Gdk.KEY_Insert: Key.INSERT,
     Gdk.KEY_Delete: Key.DELETE,
+    Gdk.KEY_Begin: Key.BEGIN,
+    Gdk.KEY_Menu: Key.MENU,
     Gdk.KEY_Home: Key.HOME,
     Gdk.KEY_End: Key.END,
     Gdk.KEY_Page_Up: Key.PAGE_UP,
@@ -130,6 +134,7 @@
     Gdk.KEY_Right: Key.RIGHT,
     Gdk.KEY_Up: Key.UP,
     Gdk.KEY_Down: Key.DOWN,
+    Gdk.KEY_Num_Lock: Key.NUMLOCK,
     Gdk.KEY_KP_Enter: Key.NUMPAD_ENTER,
     Gdk.KEY_KP_0: Key.NUMPAD_0,
     Gdk.KEY_KP_1: Key.NUMPAD_1,
@@ -150,11 +155,23 @@
     Gdk.KEY_KP_Up: Key.UP,
     Gdk.KEY_KP_Down: Key.DOWN,
     Gdk.KEY_KP_Delete: Key.DELETE,
+    Gdk.KEY_KP_Insert: Key.INSERT,
     Gdk.KEY_KP_Add: Key.NUMPAD_PLUS,
     Gdk.KEY_KP_Subtract: Key.NUMPAD_MINUS,
     Gdk.KEY_KP_Multiply: Key.NUMPAD_MULTIPLY,
     Gdk.KEY_KP_Divide: Key.NUMPAD_DIVIDE,
-    Gdk.KEY_KP_Delete: Key.DELETE,
+    Gdk.KEY_KP_Begin: Key.BEGIN,
+    Gdk.KEY_Pause: Key.PAUSE,
+    Gdk.KEY_ISO_Left_Tab: Key.TAB,
+    Gdk.KEY_Scroll_Lock: Key.SCROLLLOCK,
+    Gdk.KEY_Shift_L: Key.SHIFT,
+    Gdk.KEY_Shift_R: Key.SHIFT,
+    Gdk.KEY_Control_L: Key.MOD_1,
+    Gdk.KEY_Control_R: Key.MOD_1,
+    Gdk.KEY_Alt_L: Key.MOD_2,
+    Gdk.KEY_Alt_R: Key.MOD_2,
+    Gdk.KEY_Hyper_L: Key.MOD_3,
+    Gdk.KEY_Hyper_R: Key.MOD_3,
 }
 
 GTK_KEY_NAMES = {
@@ -172,7 +189,14 @@
 
 def toga_key(event):
     """Convert a GDK Key Event into a Toga key."""
-    key = GDK_KEYS[event.keyval]
+    try:
+        key = GDK_KEYS[event.keyval]
+    except KeyError:  # pragma: no cover
+        # Ignore any key event code we can't map. This can happen for weird key
+        # combination (ctrl-alt-tux), and if the X server has weird key
+        # bindings. If we can't map it, we can't really type it either, so we
+        # need to no-cover this branch.
+        return None
 
     modifiers = set()
 
diff --git a/winforms/src/toga_winforms/keys.py b/winforms/src/toga_winforms/keys.py
--- a/winforms/src/toga_winforms/keys.py
+++ b/winforms/src/toga_winforms/keys.py
@@ -18,6 +18,7 @@
     # Key.BACK_QUOTE.value: WinForms.Keys.Oemtilde,  # No idea what the code should be
     Key.MINUS.value: WinForms.Keys.OemMinus,
     Key.EQUAL.value: WinForms.Keys.Oemplus,
+    Key.CAPSLOCK.value: WinForms.Keys.CapsLock,
     Key.TAB.value: WinForms.Keys.Tab,
     Key.OPEN_BRACKET.value: WinForms.Keys.OemOpenBrackets,
     Key.CLOSE_BRACKET.value: WinForms.Keys.OemCloseBrackets,
@@ -30,13 +31,18 @@
     Key.SPACE.value: WinForms.Keys.Space,
     Key.PAGE_UP.value: WinForms.Keys.PageUp,
     Key.PAGE_DOWN.value: WinForms.Keys.PageDown,
+    Key.INSERT.value: WinForms.Keys.Insert,
+    Key.DELETE.value: WinForms.Keys.Delete,
     Key.HOME.value: WinForms.Keys.Home,
     Key.END.value: WinForms.Keys.End,
     Key.UP.value: WinForms.Keys.Up,
     Key.DOWN.value: WinForms.Keys.Down,
     Key.LEFT.value: WinForms.Keys.Left,
     Key.RIGHT.value: WinForms.Keys.Right,
+    Key.NUMLOCK.value: WinForms.Keys.NumLock,
     Key.NUMPAD_DECIMAL_POINT.value: WinForms.Keys.Decimal,
+    Key.SCROLLLOCK.value: WinForms.Keys.Scroll,
+    Key.MENU.value: WinForms.Keys.Menu,
 }
 WINFORMS_KEYS.update(
     {str(digit): getattr(WinForms.Keys, f"D{digit}") for digit in range(10)}


Test patch: