Repository: pwr-Solaar/Solaar

Issue:
KeyPress: [less] produces a "z" rather than a "<"
**Information**
<!-- Make sure that your issue is not one of the known issues in the Solaar documentation at https://pwr-solaar.github.io/Solaar/ -->
<!-- Do not bother opening an issue for a version older than 1.1.0.  Upgrade to the latest version and see if your issue persists. -->
<!-- If you not running the current version of Solaar, strongly consider upgrading to the newest version. -->
- Solaar version: 1.1.3rc3
- Distribution: Arch Linux
- Kernel version: `Linux aura 5.17.4-arch1-1`
- Output of `solaar show`:

  <details>
  
  ```
  ➜ solaar show
  Solaar version 1.1.3rc3
  
  Unifying Receiver
    Device path  : /dev/hidraw11
    USB id       : 046d:C52B
    Serial       : A7599671
      Firmware   : 24.01.B0023
      Bootloader : 01.08
      Other      : AA.AC
    Has 1 paired device(s) out of a maximum of 6.
    Notifications: wireless, software present (0x000900)
    Device activity counters: (empty)
  
    1: Wireless Touch Keyboard K400 Plus
       Device path  : /dev/hidraw12
       WPID         : 404D
       Codename     : K400 Plus
       Kind         : keyboard
       Protocol     : HID++ 2.0
       Polling rate : 8 ms (125Hz)
       Serial number: 23C01660
       The power switch is located on the top edge.
       Battery: unknown (device is offline).
  
  Lightspeed Receiver
    Device path  : /dev/hidraw17
    USB id       : 046d:C541
    Serial       : 1E6F6B7F
      Firmware   : 01.02.B0028
      Bootloader : 01.27
      Other      : A6.FD
    Has 1 paired device(s) out of a maximum of 2.
    Notifications: wireless, software present (0x000900)
    Device activity counters: 1=1
  
    1: G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD
       Device path  : None
       WPID         : 407C
       Codename     : G915 KEYBOARD
       Kind         : keyboard
       Protocol     : HID++ 4.2
       Polling rate : 8 ms (125Hz)
       Serial number: 460DF756
       Model ID:      B354407CC33E
       Unit ID:       460DF756
          Bootloader: BOT 77.02.B0039
               Other:
            Firmware: MPK 09.03.B0041
               Other:
               Other:
       The power switch is located on the top left corner.
       Supports 38 HID++ 2.0 features:
           0: ROOT                   {0000}
           1: FEATURE SET            {0001}
           2: DEVICE FW VERSION      {0003}
              Firmware: Bootloader BOT 77.02.B0039 0000EC44D534
              Firmware: Other
              Firmware: Firmware MPK 09.03.B0041 407C3791543D
              Firmware: Other
              Firmware: Other
              Unit ID: 460DF756  Model ID: B354407CC33E  Transport IDs: {'btleid': 'B354', 'wpid': '407C', 'usbid': 'C33E'}
           3: DEVICE NAME            {0005}
              Name: G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD
              Kind: keyboard
           4: WIRELESS DEVICE STATUS {1D4B}
           5: RESET                  {0020}
           6: DEVICE FRIENDLY NAME   {0007}
              Friendly Name: G915 KEYBOARD
           7: BATTERY VOLTAGE        {1001}
              Battery: 3765mV, discharging, 30.
           8: CHANGE HOST            {1814}
              Change Host        : 1:aura
           9: HOSTS INFO             {1815}
              Host 0 (paired): aura
              Host 1 (paired):
          10: RGB EFFECTS            {8071}
          11: PER KEY LIGHTING V2    {8081}
          12: REPROG CONTROLS V4     {1B04}
              Key/Button Diversion (saved): {Host Switch Channel 1:Regular, Host Switch Channel 2:Regular}
              Key/Button Diversion        : {Host Switch Channel 1:Regular, Host Switch Channel 2:Regular}
          13: REPORT HID USAGE       {1BC0}
          14: ENCRYPTION             {4100}
          15: KEYBOARD DISABLE BY USAGE {4522}
          16: KEYBOARD LAYOUT 2      {4540}
          17: GKEY                   {8010}
              Divert G Keys (saved): True
              Divert G Keys        : False
          18: MKEYS                  {8020}
              M-Key LEDs (saved): {M1:False, M2:False, M3:False}
              M-Key LEDs        : {M1:False, M2:False, M3:False}
          19: MR                     {8030}
              MR-Key LED (saved): False
              MR-Key LED        : False
          20: BRIGHTNESS CONTROL     {8040}
          21: ONBOARD PROFILES       {8100}
              Device Mode: On-Board
              Onboard Profiles (saved): Enable
              Onboard Profiles        : Enable
          22: REPORT RATE            {8060}
              Polling Rate (ms): 1
              Polling Rate (ms) (saved): 1
              Polling Rate (ms)        : 1
          23: DFUCONTROL SIGNED      {00C2}
          24: DFU                    {00D0}
          25: DEVICE RESET           {1802}   internal, hidden
          26: unknown:1803           {1803}   internal, hidden
          27: CONFIG DEVICE PROPS    {1806}   internal, hidden
          28: unknown:1813           {1813}   internal, hidden
          29: OOBSTATE               {1805}   internal, hidden
          30: unknown:1830           {1830}   internal, hidden
          31: unknown:1890           {1890}   internal, hidden
          32: unknown:1891           {1891}   internal, hidden
          33: unknown:18A1           {18A1}   internal, hidden
          34: unknown:1E00           {1E00}   hidden
          35: unknown:1EB0           {1EB0}   internal, hidden
          36: unknown:1861           {1861}   internal, hidden
          37: unknown:18B0           {18B0}   internal, hidden
       Has 2 reprogrammable keys:
           0: Host Switch Channel 1     , default: HostSwitch Channel 1        => HostSwitch Channel 1
               divertable, persistently divertable, pos:1, group:0, group mask:empty
               reporting: default
           1: Host Switch Channel 2     , default: HostSwitch Channel 2        => HostSwitch Channel 2
               divertable, persistently divertable, pos:2, group:0, group mask:empty
               reporting: default
       Battery: 3765mV, discharging, 30.
  
  Lightspeed Receiver
    Device path  : /dev/hidraw3
    USB id       : 046d:C539
    Serial       : EDC69B5D
      Firmware   : 39.05.B0040
      Bootloader : 01.08
      Other      : AA.BE
    Has 1 paired device(s) out of a maximum of 1.
    Notifications: wireless, software present (0x000900)
    Device activity counters: (empty)
  
    1: G900 Chaos Spectrum Gaming Mouse
       Device path  : /dev/hidraw5
       WPID         : 4053
       Codename     : G900
       Kind         : mouse
       Protocol     : HID++ 4.2
       Polling rate : 8 ms (125Hz)
       Serial number: 5BFEBB5F
       Model ID:      C08100000000
       Unit ID:       5BFEBB5F
            Firmware: MPM 01.05.B0023
          Bootloader: BOT 20.02.B0020
               Other:
       The power switch is located on the base.
       Supports 25 HID++ 2.0 features:
           0: ROOT                   {0000}
           1: FEATURE SET            {0001}
           2: DEVICE FW VERSION      {0003}
              Firmware: Firmware MPM 01.05.B0023 C081
              Firmware: Bootloader BOT 20.02.B0020 AABA
              Firmware: Other
              Unit ID: 5BFEBB5F  Model ID: C08100000000  Transport IDs: {'usbid': 'C081'}
           3: DEVICE NAME            {0005}
              Name: Gaming Mouse G900
              Kind: mouse
           4: BATTERY VOLTAGE        {1001}
              Battery: 3697mV, discharging, 10.
           5: unknown:1863           {1863}   internal, hidden
           6: unknown:18A1           {18A1}   internal, hidden
           7: unknown:1E00           {1E00}   hidden
           8: unknown:1E20           {1E20}
           9: unknown:1EB0           {1EB0}   internal, hidden
          10: ADJUSTABLE DPI         {2201}
              Sensitivity (DPI) (saved): 1000
              Sensitivity (DPI)        : 1000
          11: ANGLE SNAPPING         {2230}
          12: SURFACE TUNING         {2240}
          13: REPORT RATE            {8060}
              Polling Rate (ms): 2
              Polling Rate (ms) (saved): 2
              Polling Rate (ms)        : 2
          14: ONBOARD PROFILES       {8100}
              Device Mode: Host
              Onboard Profiles (saved): Disable
              Onboard Profiles        : Disable
          15: MOUSE BUTTON SPY       {8110}
          16: unknown:1850           {1850}   internal, hidden
          17: DFUCONTROL SIGNED      {00C2}
          18: unknown:1801           {1801}   internal, hidden
          19: DEVICE RESET           {1802}   internal, hidden
          20: unknown:1890           {1890}   internal, hidden
          21: unknown:1811           {1811}   internal, hidden
          22: LATENCY MONITORING     {8111}
          23: COLOR LED EFFECTS      {8070}
          24: LED CONTROL            {1300}
       Battery: 3697mV, discharging, 10.
  
  Lightspeed Receiver
    Device path  : /dev/hidraw8
    USB id       : 046d:C545
    Serial       : B745659B
      Firmware   : 02.01.B0006
      Bootloader : 00.01
      Other      : 90.2A
    Has 1 paired device(s) out of a maximum of 2.
    Notifications: wireless, software present (0x000900)
    Device activity counters: 1=162
  
    1: G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard
       Device path  : None
       WPID         : 408E
       Codename     : G915 TKL
       Kind         : keyboard
       Protocol     : HID++ 4.2
       Polling rate : 8 ms (125Hz)
       Serial number: E39C9BCF
       Model ID:      B35F408EC343
       Unit ID:       E39C9BCF
          Bootloader: BL1 12.00.B0017
               Other:
            Firmware: MPK 14.01.B0021
               Other:
               Other:
       The power switch is located on the top left corner.
       Supports 38 HID++ 2.0 features:
           0: ROOT                   {0000}
           1: FEATURE SET            {0001}
           2: DEVICE FW VERSION      {0003}
              Firmware: Bootloader BL1 12.00.B0017 00008B79E978
              Firmware: Other
              Firmware: Firmware MPK 14.01.B0021 408E5E599E54
              Firmware: Other
              Firmware: Other
              Unit ID: E39C9BCF  Model ID: B35F408EC343  Transport IDs: {'btleid': 'B35F', 'wpid': '408E', 'usbid': 'C343'}
           3: DEVICE NAME            {0005}
              Name: G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard
              Kind: keyboard
           4: WIRELESS DEVICE STATUS {1D4B}
           5: RESET                  {0020}
           6: DEVICE FRIENDLY NAME   {0007}
              Friendly Name: G915 TKL
           7: BATTERY VOLTAGE        {1001}
              Battery: 4194mV, full, 100.
           8: CHANGE HOST            {1814}
              Change Host        : 1:aura
           9: HOSTS INFO             {1815}
              Host 0 (paired): aura
              Host 1 (unpaired):
          10: RGB EFFECTS            {8071}
          11: PER KEY LIGHTING V2    {8081}
          12: REPROG CONTROLS V4     {1B04}
              Key/Button Diversion (saved): {Host Switch Channel 1:Regular, Host Switch Channel 2:Regular}
              Key/Button Diversion        : {Host Switch Channel 1:Regular, Host Switch Channel 2:Regular}
          13: REPORT HID USAGE       {1BC0}
          14: ENCRYPTION             {4100}
          15: KEYBOARD DISABLE BY USAGE {4522}
          16: KEYBOARD LAYOUT 2      {4540}
          17: GKEY                   {8010}
              Divert G Keys (saved): False
              Divert G Keys        : False
          18: MKEYS                  {8020}
              M-Key LEDs (saved): {M1:False, M2:False, M3:False}
              M-Key LEDs        : {M1:False, M2:False, M3:False}
          19: MR                     {8030}
              MR-Key LED (saved): False
              MR-Key LED        : False
          20: BRIGHTNESS CONTROL     {8040}
          21: ONBOARD PROFILES       {8100}
              Device Mode: On-Board
              Onboard Profiles (saved): Enable
              Onboard Profiles        : Enable
          22: REPORT RATE            {8060}
              Polling Rate (ms): 1
              Polling Rate (ms) (saved): 1
              Polling Rate (ms)        : 1
          23: DFUCONTROL SIGNED      {00C2}
          24: DFU                    {00D0}
          25: DEVICE RESET           {1802}   internal, hidden
          26: unknown:1803           {1803}   internal, hidden
          27: CONFIG DEVICE PROPS    {1806}   internal, hidden
          28: unknown:1813           {1813}   internal, hidden
          29: OOBSTATE               {1805}   internal, hidden
          30: unknown:1830           {1830}   internal, hidden
          31: unknown:1890           {1890}   internal, hidden
          32: unknown:1891           {1891}   internal, hidden
          33: unknown:18A1           {18A1}   internal, hidden
          34: unknown:1E00           {1E00}   hidden
          35: unknown:1EB0           {1EB0}   internal, hidden
          36: unknown:1861           {1861}   internal, hidden
          37: unknown:18B0           {18B0}   internal, hidden
       Has 2 reprogrammable keys:
           0: Host Switch Channel 1     , default: HostSwitch Channel 1        => HostSwitch Channel 1
               divertable, persistently divertable, pos:1, group:0, group mask:empty
               reporting: default
           1: Host Switch Channel 2     , default: HostSwitch Channel 2        => HostSwitch Channel 2
               divertable, persistently divertable, pos:2, group:0, group mask:empty
               reporting: default
       Battery: 4194mV, discharging, 100.
  ```
  </details>

- Contents of `~/.config/solaar/config.yaml` (or `~/.config/solaar/config.json` if `~/.config/solaar/config.yaml` not present):

  <details>
  
  ```
  - 1.1.3rc3
  - _NAME: Wireless Touch Keyboard K400 Plus
    _modelId: 404D00000000
    _serial: 23C01660
    _unitId: 0E91098D
    _wpid: 404D
    disable-keyboard-keys: {1: false, 4: false, 8: false, 16: false}
    divert-keys: {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 8: 0, 34: 0, 40: 0, 62: 0, 64: 0, 79: 0, 84: 0, 110: 0, 145: 0, 149: 0, 183: 0, 184: 0}
    fn-swap: true
    gesture2-gestures: {1: true, 2: true, 10: true, 30: true, 34: false, 42: true, 43: true, 45: false, 64: false, 65: false, 67: false, 84: true}
    gesture2-params:
      4: {scale: 256}
  - _NAME: G900 Chaos Spectrum Gaming Mouse
    _absent: [hi-res-scroll, lowres-smooth-scroll, hires-smooth-invert, hires-smooth-resolution, smart-shift, thumb-scroll-mode, thumb-scroll-invert, pointer_speed,
      dpi-sliding, speed-change, mouse-gestures, backlight, backlight-timed, fn-swap, reprogrammable-keys, persistent-remappable-keys, divert-keys, disable-keyboard-keys,
      divert-crown, crown-smooth, divert-gkeys, m-key-leds, mr-key-led, multiplatform, change-host, gesture2-gestures, gesture2-divert, gesture2-params, lowres-scroll-mode,
      hires-scroll-mode]
    _modelId: C08100000000
    _sensitive: {report_rate: false}
    _serial: 5BFEBB5F
    _unitId: 5BFEBB5F
    _wpid: '4053'
    dpi: 1000
    onboard_profiles: 2
    report_rate: 2
  - _NAME: M720 Triathlon Multi-Device Mouse
    _modelId: B015405E0000
    _serial: 83958AD8
    _unitId: A5676EEB
    _wpid: 405E
    divert-keys: {82: 0, 83: 0, 86: 0, 91: 0, 93: 0, 208: 0}
    hires-smooth-invert: false
    hires-smooth-resolution: true
    mouse-gestures: 0
    pointer_speed: 256
    reprogrammable-keys: {80: 80, 81: 81, 82: 82, 83: 83, 86: 86, 91: 91, 93: 93, 208: 208}
  - _NAME: G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD
    _absent: [hi-res-scroll, lowres-smooth-scroll, hires-smooth-invert, hires-smooth-resolution, smart-shift, thumb-scroll-mode, thumb-scroll-invert, pointer_speed,
      dpi, dpi-sliding, speed-change, mouse-gestures, backlight, backlight-timed, fn-swap, reprogrammable-keys, persistent-remappable-keys, disable-keyboard-keys,
      divert-crown, crown-smooth, multiplatform, gesture2-gestures, gesture2-divert, gesture2-params, lowres-scroll-mode, hires-scroll-mode]
    _modelId: B354407CC33E
    _sensitive: {change-host: ignore, divert-gkeys: true, divert-keys: false, report_rate: ignore}
    _serial: 460DF756
    _unitId: 460DF756
    _wpid: 407C
    divert-gkeys: true
    divert-keys: {209: 0, 210: 0}
    m-key-leds: {1: false, 2: false, 4: false}
    mr-key-led: false
    onboard_profiles: 1
    report_rate: 1
  - _NAME: MX Keys Keyboard
    _modelId: B35B408A0000
    _serial: '97624246'
    _unitId: '97624246'
    _wpid: 408A
    backlight: true
    disable-keyboard-keys: {1: false, 2: false, 4: false, 8: false, 16: false}
    divert-keys: {10: 0, 110: 0, 111: 0, 191: 0, 199: 0, 200: 0, 209: 0, 210: 0, 211: 0, 224: 0, 225: 0, 226: 0, 227: 0, 228: 0, 229: 0, 230: 0, 231: 0, 232: 0,
      233: 0, 234: 0, 235: 0, 236: 0}
    fn-swap: false
    multiplatform: 0
  - _NAME: G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard
    _absent: [hi-res-scroll, lowres-smooth-scroll, hires-smooth-invert, hires-smooth-resolution, smart-shift, thumb-scroll-mode, thumb-scroll-invert, pointer_speed,
      dpi, dpi-sliding, speed-change, mouse-gestures, backlight, backlight-timed, fn-swap, reprogrammable-keys, persistent-remappable-keys, disable-keyboard-keys,
      divert-crown, crown-smooth, multiplatform, gesture2-gestures, gesture2-divert, gesture2-params, lowres-scroll-mode, hires-scroll-mode]
    _modelId: B35F408EC343
    _serial: E39C9BCF
    _unitId: E39C9BCF
    _wpid: 408E
    divert-gkeys: false
    divert-keys: {209: 0, 210: 0}
    m-key-leds: {1: false, 2: false, 4: false}
    mr-key-led: false
    onboard_profiles: 1
    report_rate: 1
  ```
  </details>


- Errors or warrnings from Solaar:

  \-



**Describe the bug**

I am trying to type out a `<` character using a rule. The rule looks like this:

```yaml
---
- Key: [G5, pressed]
- KeyPress: [less]
```

Instead of typing a `<`, this types out a `z`, which is the key right next to `<`.

**To Reproduce**
Steps to reproduce the behavior:
- Create a rule
- Use `KeyPress: [less]`
- Execute the rule in a text editor
- It should produce `<`, instead it produces `z`

**Screenshots**

\-

**Additional context**

Nordic keyboard, if that matters.



Discussion:
Solaar simulates character input by simulating the press and release of keyboard keys which are roughly identified by their location. Solaar tries to determine which keyboard key corresponds to which character but this does not always work, particularly under Wayland.  Conditions under which the determination can (but doesn't always) go wrong include using non-QWERTY keyboards or switching between keyboard groups.

As you are using a Nordic keyboard, this is likely the problem.  Solaar determines that on your keyboard the key that produces < is near the lower-left but the mechanism it uses to simulate keyboard input uses a US QWERTY mapping.

If you are willing to do some testing, it should be possible to make Solaar work better here.  The first thing to do is to run Solaar as `solaar -ddd`, make the rule run, and post the output.

@pfps The output from running `solaar -ddd` and pressing the G5 key is:

```
10:25:06,099    DEBUG [ReceiverListener:hidraw17] logitech_receiver.base: (19) => r[11 01 1100 10000000000000000000000000000000]
10:25:06,099    DEBUG [ReceiverListener:hidraw17] logitech_receiver.notifications: <Device(1,407C,G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD,460DF756)>: notification for feature GKEY, report 1, data 10000000000000000000000000000000
10:25:06,099     INFO [MainThread] logitech_receiver.diversion: KeyPress action: ['less'], modifiers 16 ['0x3c']
10:25:06,100     INFO [MainThread] logitech_receiver.diversion: XKB display set up
10:25:06,100    DEBUG [MainThread] logitech_receiver.diversion: uinput simulated input 1 44 1
10:25:06,100    DEBUG [MainThread] logitech_receiver.diversion: uinput simulated input 1 44 0
10:25:06,227    DEBUG [ReceiverListener:hidraw17] logitech_receiver.base: (19) => r[11 01 1100 00000000000000000000000000000000]
10:25:06,227    DEBUG [ReceiverListener:hidraw17] logitech_receiver.notifications: <Device(1,407C,G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD,460DF756)>: notification for feature GKEY, report 1, data 00000000000000000000000000000000
```

This is what `xev` produces when I press the `<` key:

```
KeyPress event, serial 34, synthetic NO, window 0x6200001,
    root 0x3cc, subw 0x0, time 812692392, (96,85), root:(2567,702),
    state 0x10, keycode 94 (keysym 0x3c, less), same_screen YES,
    XLookupString gives 1 bytes: (3c) "<"
    XmbLookupString gives 1 bytes: (3c) "<"
    XFilterEvent returns: False
```

I am indeed running Wayland (Sway). I assumed this didn't matter, since I thought the key press was being simulated by the driver anyway.
Input processing is complex and is done in several places.  What can be simulated is not inputting characters but instead pushing and releasing keyboard keys (as keycodes).  There are two ways to do this - XTest in X11 and uinput.  Solaar uses both, depending on what is available.   On Wayland Solaar only uses uinput.
These keycode presses and releases are then sent into the windowing system and programs can see them directly, see a slight interpretation of them, or just see a stream of characters.  Also, the keys are not things like the A key, but instead are something like "the key that is the left-most character key on the middle row of the keyboard keys".  X11 and Wayland both use the X Keyboard Extension for this purpose but don't work exactly the same.

What would be nice would be if there was a way to set up a separate keyboard whose interpretation doesn't change but this doesn't seem to be possible so Solaar has to try to figure out what keycode corresponds to what symbol in a way that works in both X11 and Wayland.

What  Solaar appears to see is several keycodes that can be used for < and picks one that doesn't work.   To make this work better, I'll create an instrumented version of Solaar that shows the information that Solaar uses in your setup.


Resolving patch:
diff --git a/lib/logitech_receiver/diversion.py b/lib/logitech_receiver/diversion.py
--- a/lib/logitech_receiver/diversion.py
+++ b/lib/logitech_receiver/diversion.py
@@ -836,14 +836,14 @@ def __init__(self, keys):
     def keysym_to_keycode(self, keysym, modifiers):  # maybe should take shift into account
         group = kbdgroup() or 0
         keycodes = gkeymap.get_entries_for_keyval(keysym)
-        if len(keycodes.keys) == 1:
-            k = keycodes.keys[0]
-            return k.keycode
-        else:
-            for k in keycodes.keys:
-                if group is None or group == k.group:
-                    return k.keycode
+        (keycode, level) = (None, None)
+        for k in keycodes.keys:
+            if (group == k.group or len(keycodes.keys) == 1) and k.keycode < 256 and (level is None or k.level < level):
+                keycode = k.keycode
+                level = k.level
+        if keycode is None:
             _log.warn('rule KeyPress key symbol not currently available %s', self)
+        return (keycode, level)
 
     def __str__(self):
         return 'KeyPress: ' + ' '.join(self.key_names)
@@ -852,17 +852,29 @@ def needed(self, k, modifiers):
         code = modifier_code(k)
         return not (code is not None and modifiers & (1 << code))
 
+    def mods(self, level, modifiers, direction):
+        if level == 2 or level == 3:
+            (sk, _) = self.keysym_to_keycode(XK_KEYS.get('ISO_Level3_Shift', None), modifiers)
+            if sk and self.needed(sk, modifiers):
+                simulate_key(sk, direction)
+        if level == 1 or level == 3:
+            (sk, _) = self.keysym_to_keycode(XK_KEYS.get('Shift_L', None), modifiers)
+            if sk and self.needed(sk, modifiers):
+                simulate_key(sk, direction)
+
     def keyDown(self, keysyms, modifiers):
         for k in keysyms:
-            keycode = self.keysym_to_keycode(k, modifiers)
+            (keycode, level) = self.keysym_to_keycode(k, modifiers)
             if keycode and self.needed(keycode, modifiers):
+                self.mods(level, modifiers, _KEY_PRESS)
                 simulate_key(keycode, _KEY_PRESS)
 
     def keyUp(self, keysyms, modifiers):
         for k in keysyms:
-            keycode = self.keysym_to_keycode(k, modifiers)
+            (keycode, level) = self.keysym_to_keycode(k, modifiers)
             if keycode and self.needed(keycode, modifiers):
                 simulate_key(keycode, _KEY_RELEASE)
+                self.mods(level, modifiers, _KEY_RELEASE)
 
     def evaluate(self, feature, notification, device, status, last_result):
         if gkeymap:


Test patch: