Solaar: Reprogrammable keys do not work on K400+

Created on 17 Jun 2020  路  21Comments  路  Source: pwr-Solaar/Solaar

Information

  • Solaar version: 1.0.2-18-g5926596
  • Distribution: Linux Mint
  • Kernel version: 4.15.0-106
  • Output of solaar show:
Unifying Receiver
  Device path  : /dev/hidraw3
  USB id       : 046d:c52b
  Serial       : BE57977A
    Firmware   : 24.11.B0036
    Bootloader : 02.09
    Other      : AA.AC
  Has 1 paired device(s) out of a maximum of 6.
  Notifications: wireless, software present (0x000900)
  Device activity counters: 1=193

  1: Wireless Touch Keyboard K400 Plus
     Codename     : K400 Plus
     Kind         : keyboard
     Wireless PID : 404D
     Protocol     : HID++ 4.1
     Polling rate : 8 ms (125Hz)
     Serial number: 6DC5C81C
        Bootloader: BOT 22.02.B0002
          Firmware: RQK 63.02.B0016
             Other:
     The power switch is located on the top edge.
     Supports 24 HID++ 2.0 features:
         0: ROOT                   {0000}
         1: FEATURE SET            {0001}
         2: DEVICE FW VERSION      {0003}
         3: DEVICE NAME            {0005}
         4: WIRELESS DEVICE STATUS {1D4B}
         5: RESET                  {0020}
         6: BATTERY STATUS         {1000}
         7: REPROG CONTROLS V4     {1B04}
            Actions: {'84': NamedInt(84, 'Mouse Back Button'), '34': NamedInt(34, 'HomePage'), '8': NamedInt(8, 'Application Switcher'), '79': NamedInt(79, 'RightClick'), '62': NamedInt(62, 'Search Files'), '110': NamedInt(110, 'Show Desktop'), '145': NamedInt(145, 'Maximize Window'), '149': NamedInt(149, 'Switch Screen'), '40': NamedInt(40, 'Music'), '64': NamedInt(64, 'Sleep')}
         8: SWAP BUTTON CANCEL     {2005}
         9: NEW FN INVERSION       {40A2}
            Swap Fx function: True
        10: ENCRYPTION             {4100}
        11: KEYBOARD DISABLE       {4521}
        12: TOUCHPAD RAW XY        {6100}
        13: GESTURE 2              {6501}
        14: DFUCONTROL UNSIGNED    {00C1}
        15: unknown:1811           {1811}   internal, hidden
        16: unknown:1830           {1830}   internal, hidden
        17: unknown:1890           {1890}   internal, hidden
        18: unknown:1DF3           {1DF3}   internal, hidden
        19: unknown:1E00           {1E00}   hidden
        20: unknown:1EB0           {1EB0}   internal, hidden
        21: unknown:1861           {1861}   internal, hidden
        22: unknown:18B0           {18B0}   internal, hidden
        23: unknown:1F11           {1F11}   internal, hidden
     Has 20 reprogrammable keys:
         0: unknown:00B8              , default: LeftClick                   => unknown:00B8
             mse, divertable, pos:0, group:0, gmask:0
         1: Back                      , default: Mouse Back Button           => Back
             is FN, FN sensitive, reprogrammable, divertable, pos:1, group:0, gmask:0
         2: MY HOME                   , default: HomePage                    => MY HOME
             is FN, FN sensitive, reprogrammable, divertable, pos:2, group:0, gmask:0
         3: Application Switcher      , default: Application Switcher        => Application Switcher
             is FN, FN sensitive, reprogrammable, divertable, pos:3, group:0, gmask:0
         4: CONTEXTUAL MENU           , default: RightClick                  => CONTEXTUAL MENU
             is FN, FN sensitive, reprogrammable, divertable, pos:4, group:0, gmask:0
         5: Search                    , default: Search Files                => Search
             is FN, FN sensitive, reprogrammable, divertable, pos:5, group:0, gmask:0
         6: Show Desktop              , default: Show Desktop                => Show Desktop
             is FN, FN sensitive, reprogrammable, divertable, pos:6, group:0, gmask:0
         7: Maximize Window           , default: Maximize Window             => Maximize Window
             is FN, FN sensitive, reprogrammable, divertable, pos:7, group:0, gmask:0
         8: Switch Screen             , default: Switch Screen               => Switch Screen
             is FN, FN sensitive, reprogrammable, divertable, pos:8, group:0, gmask:0
         9: MEDIA PLAYER              , default: Music                       => MEDIA PLAYER
             is FN, FN sensitive, reprogrammable, divertable, pos:9, group:0, gmask:0
        10: Previous                  , default: Previous                    => Previous
             is FN, FN sensitive, divertable, pos:10, group:0, gmask:0
        11: Play/Pause                , default: Play/Pause                  => Play/Pause
             is FN, FN sensitive, divertable, pos:11, group:0, gmask:0
        12: Next                      , default: Next                        => Next
             is FN, FN sensitive, divertable, pos:12, group:0, gmask:0
        13: unknown:00B7              , default: ShowUI                      => unknown:00B7
             divertable, pos:0, group:0, gmask:0
        14: LEFT CLICK                , default: LeftClick                   => LEFT CLICK
             mse, pos:0, group:0, gmask:0
        15: RIGHT CLICK               , default: RightClick                  => RIGHT CLICK
             mse, pos:0, group:0, gmask:0
        16: Mute                      , default: Mute                        => Mute
             nonstandard, divertable, pos:0, group:0, gmask:0
        17: Volume Up                 , default: Volume Up                   => Volume Up
             nonstandard, divertable, pos:0, group:0, gmask:0
        18: Volume Down               , default: Volume Down                 => Volume Down
             nonstandard, divertable, pos:0, group:0, gmask:0
        19: SLEEP                     , default: Sleep                       => SLEEP
             reprogrammable, divertable, pos:0, group:0, gmask:0
     Battery: 90%, discharging, next level 50%.

Describe the bug
This is a follow-up to #810. The K400+ keyboard supports REPROG CONTROLS V4, but the current implementation doesn't work. The reprogrammable keys and their options are displayed, but only the default action is executed regardless of the chosen option.

To Reproduce
Choose a reprogrammable key and change its action. Press the key. Instead of the chosen action, the default one is performed.

Additional context

From #810:

Please remove any "reprogrammable-keys" entries from ~/.config/solaar/config.json
Then run bin/solaar -dd, try a key change, and report any errors and tracebacks encountered.

K400plus_reprogramming_test.txt

I pressed the F1/back key once on a browser, tried to change its action and pressed it again on the browser. Only the default action was executed.

Then report the contents of ~/.config/solaar/config.json

{
  "404D:6DC5C81C": {
    "_name": "Wireless Touch Keyboard K400 Plus",
    "fn-swap": true,
    "reprogrammable-keys": {
      "110": 110,
      "145": 145,
      "149": 149,
      "34": 34,
      "40": 40,
      "62": 62,
      "64": 64,
      "79": 79,
      "8": 8,
      "84": 8
    }
  },
  "_version": "1.0.2"
}

By the way, I've noticed that several keystrokes (multimedia keys and other special buttons) are incorrectly interpreted by Solaar as notifications for DEVICE NAME, for example:

WARNING [ReceiverListener:hidraw3] logitech_receiver.notifications: <PairedDevice(1,404D,K400 Plus,6DC5C81C)>: unrecognized Notification(1,03,00,0000000000003D00000000) for feature DEVICE NAME (index 03)

In the following, in both read and written bytes, I'll omit the trailing zeroes in the data, the prefix (10/11), the device number (e.g. 01) and the feature index (e.g. 07; the one that refers to 1B 04 = REPROG CONTROLS V4 on the device), and I'll use s to represent the software ID (4-bit, apparently chosen arbitrarily).

The way Solaar tries to reprogram a key XX XX to an action YY YY (3s XX XX 00 YY YY) is ignored with no errors, and this is probably the reason for the unexpected group 0 and gmask 0: it expects another pattern.

By using Wireshark and observing what happens when Logitech Options is running on a Windows VM, I figured this out:

  • When we reprogram a key, LO just sends 3s XX XX 03 regardless of the chosen action. After that, the key no longer sends its normal value after being pressed; instead, it just sends 00 XX XX. At this point, if the Logitech processes are killed (or if the keyboard is transferred back to my Linux host), the key stops working, possibly because the data isn't recognised by the regular drivers and the keystrokes are actually handled by Logitech software. This does not persist after the device is powered off, and I found no other way of reverting this [EDIT: it can be reverted by sending the same bytes replacing 03 with 02].
  • When the key is released, it sends 00 00 00.
  • If reprogrammed keys are simultaneously pressed, they're added to the end: 00 XX XX ZZ ZZ WW WW... (real example: 00 00 54 00 08 00 22 00 3E), which could be used to create combinations of those keys. This isn't used by Logitech Options.
  • I've tested that all of this works with all divertable keys, even the non-programmable ones that LO doesn't allow to remap (including the yellow button). Therefore, it is possible to read that and execute any action such as sending arbitrary key combinations or running a program.

Just as a proof of concept, I temporarily added the following code to _process_feature_notification():

    if feature == _F.REPROG_CONTROLS_V4:
        if n.address == 0x00 and len(n.data) == 16:
            if _log.isEnabledFor(_INFO):
                _log.info("%s: reprogrammable key: %s", device, n)
                for i in range(8):
                    k = (n.data[2*i] << 8) | n.data[2*i + 1]  # two bytes
                    if k != 0:
                        _log.debug('JUST A TEST: pressed the key 0x%04X' % k)
        else:
            _log.warn("%s: unknown REPROGRAMMABLE KEYS V4 %s", device, n)
        return True

Now, after I manually send 0s XX XX 03 once for some of the divertable keys, they no longer execute the default actions, and their codes are logged whenever they're pressed.

To implement this feature, Solaar would have to re-implement all of the actions, just like Logitech Option does (it sends 0s XX XX 03 to all divertable keys on startup). To do this, Solaar would have to simulate keystrokes and mouse clicks. I'm not sure about the best way to do it from Python without depending on the Linux distribution or the destkop environment. Adding an external library as a dependency might help, but I'm not sure if it's worth it.

For now, regardless of whether this will be implemented, I think those "0" settings should be hidden, so that the user doesn't see options that produce no effect.

enhancement

All 21 comments

I'm not sure what that notification is for. If I had more examples maybe I could make a guess. Including the messages just before (the raw message information) would help.

Solaar tries to make sense of what it sees from the device, based mostly on documentation from Logitech, but there are lots of things that it has to just give up on.

What Solaar is trying to do is to reprogram the key, which changes the bits that it sends. This does not appear to be working for the K400 Plus.
What LO is doing is diverting the key, which as you have found out makes the key send back HID++ 2.0 notifications (with codes for up to four keys) which are handled specially by some Logitech software which presumably has remembered what has been set up for the key and does that. Getting Solaar to do that isn't going to be easy.

The reprogrammable flag for 10 of the K400 Plus keys is presumably on because they can be reprogrammed, but the usual way for reprogramming them isn't working for some reason. I'm trying to get information that might help, but this is likely to take some time.

I would like to keep the 0 gmask stuff in for a few days, just in case there is some keyboard where it works. You can undo this processing by changing

            for g in range(0,9):

to

            for g in range(1,9):

in settings_templates.py

One thing to note is that the device is supposed to respond to a successful reprogramming with the exact same information, but the response is slightly different
` 20:25:06,861 DEBUG [AsyncUI] logitech_receiver.base: (11) <= w[11 01 073F 00540000080000000000000000000000] 20:25:06,919 DEBUG [AsyncUI] logitech_receiver.base: (11) => r[11 01 073F 00540000000000000000000000000000]
I don't view this is as a problem because lots of other responses are different in this way (my mouse also responds to successful key reprogramming with a response different from the command).

I'm not sure what that notification is for.

The unknown notification warnings aren't a problem, and I only mentioned them because I noticed them when I was testing some keys. It looks like the notifications sent when some special keys get released are incorrectly interpreted by Solaar as begin about "device name".

If I had more examples maybe I could make a guess. Including the messages just before (the raw message information) would help.

Below, the 1st line is the key press, the 2nd line is the key release, and the 3rd line is the warning it causes. The pattern is repeated for other keys.

10:03:46,000    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 03B6 0000000000008700000000]
10:03:46,144    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 0300 0000000000003D00000000]
10:03:46,144  WARNING [ReceiverListener:hidraw3] logitech_receiver.notifications: <PairedDevice(1,404D,K400 Plus,6DC5C81C)>: unrecognized Notification(1,03,00,0000000000003D00000000) for feature DEVICE NAME (index 03)
10:03:46,782    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 03CD 0000000000007000000000]
10:03:46,966    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 0300 0000000000003D00000000]
10:03:46,966  WARNING [ReceiverListener:hidraw3] logitech_receiver.notifications: <PairedDevice(1,404D,K400 Plus,6DC5C81C)>: unrecognized Notification(1,03,00,0000000000003D00000000) for feature DEVICE NAME (index 03)
10:03:47,538    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 03B5 0000000000008800000000]
10:03:47,724    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 0300 0000000000003D00000000]
10:03:47,724  WARNING [ReceiverListener:hidraw3] logitech_receiver.notifications: <PairedDevice(1,404D,K400 Plus,6DC5C81C)>: unrecognized Notification(1,03,00,0000000000003D00000000) for feature DEVICE NAME (index 03)
10:03:51,452    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 03E2 0000000000005B00000000]
10:03:51,564    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 0300 0000000000003D00000000]
10:03:51,564  WARNING [ReceiverListener:hidraw3] logitech_receiver.notifications: <PairedDevice(1,404D,K400 Plus,6DC5C81C)>: unrecognized Notification(1,03,00,0000000000003D00000000) for feature DEVICE NAME (index 03)
10:03:52,634    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 03EA 0000000000005300000000]
10:03:52,762    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 0300 0000000000003D00000000]
10:03:52,763  WARNING [ReceiverListener:hidraw3] logitech_receiver.notifications: <PairedDevice(1,404D,K400 Plus,6DC5C81C)>: unrecognized Notification(1,03,00,0000000000003D00000000) for feature DEVICE NAME (index 03)
10:03:53,680    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 03E9 0000000000005400000000]
10:03:53,810    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 0300 0000000000003D00000000]
10:03:53,811  WARNING [ReceiverListener:hidraw3] logitech_receiver.notifications: <PairedDevice(1,404D,K400 Plus,6DC5C81C)>: unrecognized Notification(1,03,00,0000000000003D00000000) for feature DEVICE NAME (index 03)

[...]

10:14:35,337    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 03B6 0000000500008200000000]
10:14:35,433    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 0300 0000000500003800000000]
10:14:35,434  WARNING [ReceiverListener:hidraw3] logitech_receiver.notifications: <PairedDevice(1,404D,K400 Plus,6DC5C81C)>: unrecognized Notification(1,03,00,0000000500003800000000) for feature DEVICE NAME (index 03)
10:14:35,763    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 03CD 0000000500006B00000000]
10:14:35,843    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 0300 0000000500003800000000]
10:14:35,844  WARNING [ReceiverListener:hidraw3] logitech_receiver.notifications: <PairedDevice(1,404D,K400 Plus,6DC5C81C)>: unrecognized Notification(1,03,00,0000000500003800000000) for feature DEVICE NAME (index 03)
10:14:36,133    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 03B5 0000000500008300000000]
10:14:36,223    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 0300 0000000500003800000000]
10:14:36,224  WARNING [ReceiverListener:hidraw3] logitech_receiver.notifications: <PairedDevice(1,404D,K400 Plus,6DC5C81C)>: unrecognized Notification(1,03,00,0000000500003800000000) for feature DEVICE NAME (index 03)
10:14:36,971    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 03E2 0000000500005600000000]
10:14:37,036    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 0300 0000000500003800000000]
10:14:37,036  WARNING [ReceiverListener:hidraw3] logitech_receiver.notifications: <PairedDevice(1,404D,K400 Plus,6DC5C81C)>: unrecognized Notification(1,03,00,0000000500003800000000) for feature DEVICE NAME (index 03)
10:14:37,303    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 03EA 0000000500004E00000000]
10:14:37,375    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 0300 0000000500003800000000]
10:14:37,376  WARNING [ReceiverListener:hidraw3] logitech_receiver.notifications: <PairedDevice(1,404D,K400 Plus,6DC5C81C)>: unrecognized Notification(1,03,00,0000000500003800000000) for feature DEVICE NAME (index 03)
10:14:37,575    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 03E9 0000000500004F00000000]
10:14:37,655    DEBUG [ReceiverListener:hidraw3] logitech_receiver.base: (10) => r[20 01 0300 0000000500003800000000]
10:14:37,656  WARNING [ReceiverListener:hidraw3] logitech_receiver.notifications: <PairedDevice(1,404D,K400 Plus,6DC5C81C)>: unrecognized Notification(1,03,00,0000000500003800000000) for feature DEVICE NAME (index 03)

The notification warnings are for DJ messages, which didn't exist when that part of Solaar was written. I wrote a filter to remove some of them but I didn't have documentation so I didn't want to filter out too much. Now I have documentation so I can rewrite the filter to work better.

It may be that there is no way for Solaar to reprogram keys on the K400+. I'm going to back out the changes for gmask==0 and group==0. If anyone wants to test out how to get reprogramming working for these devices, please speak up here. To see if you have an affected device, do a solaar show and look for reprogrammable keys with group:0, gmask:0

Craft/Mx Keys are affected. Would be the sniffed out packets when reprogramming occurs useful? [ from Logitech Options ]
Those keys have shittoon of reprogramming options in L_Options, customs shortcuts, custom app launch, modifier keys, some weird hybrid keystrokes

@barolo Definitely maybe. :-)

Key "reprogramming" can be done in two main ways. Telling the device to send different bits for the key, but as regular keycodes, or telling the device to send the same bits but using a different protocol (HID++ vs HID). The former doesn't need any support from input drivers (and thus is perfect for Solaar). The latter needs the input driver to intercept these reports and turn them into something else (and this is essentially outside the scope of Solaar). So if Logitech Options uses the latter, even if the former is possible, looking at the sniffed packets won't be conclusive.

But it may be the Logitech Options does actually reprogram these keys so the information could easily be helpful, I think. What you want to do is to try to reprogram one of the keys indicated as reprogrammable as one of the keys in group 0. For the K400+ this could be Search as Show Desktop.

@pfps here you go, its "show desktop" --> "search"
tried to make it as clean as possible
show_desktop_to_search.zip

Thanks. That sure looks like Logitech Options is using diversion, i.e., the thing that is outside the scope of Solaar. A good way to check for sure is to try solaar show soon after reprogramming in Logitech Options, and without turning the device off.

This is suggestive that the nice kind of key reprogramming can't be done for this device, but it is not a complete demonstration.

For the morbidly curious:
The message that appears to be doing the diversion is:
11 01 0b 3e 006e 03 00000000000000000000000000
where
11 is an HID++ long message,
01 is the device number,
0b is the feature offset (on the Craft the 11th feature is indeed REPROG CONTROLS V4)
006e is the key number (006e is Show_Desktop)
03 is divert (send a special message instead of a regular message - the input driver remembers what do with this special message)

I think that I'm gettin what you mean. solaar show is back to normal, and the shortcut is back to "show desktop".
Still, they all have "reprogrammable, divertable, persistently divertable"
This is not surprising since bunch of possible reprogramming is context dependant, i.e per app/global and must know which app is in focus. So it's tracked on the hosts side

Precisely, so my reading is that these keys should be reprogrammable, but reprogramming them doesn't seem to do anything. If you want to see if you can get the reprogramming to work you can just change one line in settings_templates.py. In

for g in range(1,9): # group 0 and gmask 0 (k[3]) don't appear to work, don't consider group 0

change the 1,9 to 0,9

That will let you attempt to reprogram any key with gmask 0 to any key in group 0. After you do, check to see if the key behaviour changes and also whether there is a change in solaar show.

Nope, no go, I think we went through this already in another thread.
Solaar show shows it as changed but it doesn't work, is unchanged

The weird thing is that Swap Fx Funntion doesn't work either, II think that I remember it working, not sure.
Cant, activate standard F1-F12 keys

That's what bugs me. There is information on the device indicating that the key was reprogrammed, but nothing changes. I'm trying to get more information, but I don't know if that will come to anything.

I've noticed that Swap Fx function doesn't work either, it's for Fn behavior isn't it? It doesn't even change state in solaar show
Sincethose are Fn keys we're trying to reprogram, maybe it's related?
Disabling backlight works

Hmm. I thought that I had Swap Fx working on one of my keyboards (not one that I have with me now though). What it should do is swap the base behaviour of the Fn keys (between F when the Fn key is unpressed and F when the Fn key is pressed). If this isn't working for you, open a new issue.

I was conflating reprogramming and remapping. Remapping is what Solaar does. A reprogrammable key is one that Logitech indicates should be given special handling, maybe by remapping or maybe by diverting or maybe by some other means. I'm going to make the (minor) changes to the code and documentation to reflect this new understanding.

The net result, however, is that Solaar can't reprogram any keys on the K400+.

PR #828 doesn't really fix this issue, but instead describes a bit about why Solaar can't remap keys for the K400+ (in docs/usage.md). Please test it out to make sure it does the right thing.

@pfps according to Logitech documentation for feature 0x1b04, only keys with non-zero value in gmask field can be reprogrammed. I think that Logitech mark this keys as "reprogrammable" to mark whot keys can change function by software command. Such key can be only diverted if they have "divertable" or "persistent" flags so instead of normal keycode pressing this key will send HID++ event.

@doctor64 Yes, that's right. I have a pending update to reflect this understanding. A previous update eliminated non-remappable keys from the setting but didn't update the documentation.

Thanks for explaining!

only keys with non-zero value in gmask field can be reprogrammed

I have a pending update to reflect this understanding.

Please test it out to make sure it does the right thing.

The additions to the text are very clear. Now we know why Logitech Options only diverts rather than remap (on this device), and it only lets the user divert the keys that are marked as reprogrammable. I guess the non-reprogrammable keys marked as divertable are intended to be diverted by third-party software for specific applications.

PR #828 doesn't really fix this issue

I think this issue can be closed, since now we know that key reprogramming on this device cannot be done (unless key diversion ever gets implemented).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cement-head picture cement-head  路  5Comments

notaduck picture notaduck  路  15Comments

electriceccentric picture electriceccentric  路  4Comments

jinmingjian picture jinmingjian  路  12Comments

LorinRicker picture LorinRicker  路  20Comments