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()
```

Resolving patch:
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
@@ -131,6 +131,30 @@
     Gdk.KEY_Up: Key.UP,
     Gdk.KEY_Down: Key.DOWN,
     Gdk.KEY_KP_Enter: Key.NUMPAD_ENTER,
+    Gdk.KEY_KP_0: Key.NUMPAD_0,
+    Gdk.KEY_KP_1: Key.NUMPAD_1,
+    Gdk.KEY_KP_2: Key.NUMPAD_2,
+    Gdk.KEY_KP_3: Key.NUMPAD_3,
+    Gdk.KEY_KP_4: Key.NUMPAD_4,
+    Gdk.KEY_KP_5: Key.NUMPAD_5,
+    Gdk.KEY_KP_6: Key.NUMPAD_6,
+    Gdk.KEY_KP_7: Key.NUMPAD_7,
+    Gdk.KEY_KP_8: Key.NUMPAD_8,
+    Gdk.KEY_KP_9: Key.NUMPAD_9,
+    Gdk.KEY_KP_Home: Key.HOME,
+    Gdk.KEY_KP_End: Key.END,
+    Gdk.KEY_KP_Page_Down: Key.PAGE_DOWN,
+    Gdk.KEY_KP_Page_Up: Key.PAGE_UP,
+    Gdk.KEY_KP_Left: Key.LEFT,
+    Gdk.KEY_KP_Right: Key.RIGHT,
+    Gdk.KEY_KP_Up: Key.UP,
+    Gdk.KEY_KP_Down: Key.DOWN,
+    Gdk.KEY_KP_Delete: Key.DELETE,
+    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,
 }
 
 GTK_KEY_NAMES = {
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
@@ -32,10 +32,21 @@
     Key.PAGE_DOWN.value: WinForms.Keys.PageDown,
     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.NUMPAD_DECIMAL_POINT.value: WinForms.Keys.Decimal,
 }
 WINFORMS_KEYS.update(
     {str(digit): getattr(WinForms.Keys, f"D{digit}") for digit in range(10)}
 )
+WINFORMS_KEYS.update(
+    {
+        getattr(Key, f"NUMPAD_{digit}").value: getattr(WinForms.Keys, f"NumPad{digit}")
+        for digit in range(10)
+    }
+)
 
 SHIFTED_KEYS = {symbol: number for symbol, number in zip("!@#$%^&*()", "1234567890")}
 SHIFTED_KEYS.update(


Test patch:
diff --git a/cocoa/tests_backend/app.py b/cocoa/tests_backend/app.py
--- a/cocoa/tests_backend/app.py
+++ b/cocoa/tests_backend/app.py
@@ -160,6 +160,8 @@ def keystroke(self, combination):
             " ": 49,
             chr(0xF708): 96,  # F5
             chr(0x2196): 115,  # Home
+            # This only works because we're *not* testing the numeric 5
+            "5": 87,
         }[key]
 
         # Add the shift modifier to disambiguate shifted keys from non-shifted
diff --git a/testbed/tests/test_keys.py b/testbed/tests/test_keys.py
--- a/testbed/tests/test_keys.py
+++ b/testbed/tests/test_keys.py
@@ -34,6 +34,8 @@
         (Key.F5, {"key": Key.F5, "modifiers": set()}),
         (Key.HOME, {"key": Key.HOME, "modifiers": set()}),
         (Key.HOME + Key.MOD_1, {"key": Key.HOME, "modifiers": {Key.MOD_1}}),
+        # Numpad keys
+        (Key.NUMPAD_5 + Key.MOD_1, {"key": Key.NUMPAD_5, "modifiers": {Key.MOD_1}}),
         # Key where platforms have odd representations
         (Key.MOD_1 + Key.SEMICOLON, {"key": Key.SEMICOLON, "modifiers": {Key.MOD_1}}),
         (Key.MOD_1 + Key.SPACE, {"key": Key.SPACE, "modifiers": {Key.MOD_1}}),