Reprogrammable keys do not work on K400+
Created by: viniciusbm
Information
- Solaar version: 1.0.2-18-g59265962
- 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 sends00 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, andI 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.