Fedora – Fn key for Bluetooth Apple Magic Keyboard (2015)

applefedorahardwarehardware-compatibilitykeyboard

I'm having trouble using my Apple Magic Keyboard (bluetooth wireless with LiIon battery, Lightning port for charging and tethered usage) with Fedora 25 (kernel: 4.8.15-300.fc25.x86_64).

The problem is that when used in wireless mode, the Fn key does not seem to register. I tried xev and the key itself doesn't trigger any event, nor does the key pressed with another key cause the triggered event to be any different compared to the other key just being pressed on its own. The reason why I'd like to use the Fn key is because I want to map Fn + / to Home and End respectively and also use the multimedia keys which are now function keys by default.

The interesting thing is that this keyboard acts as a normal Apple wired keyboard when I connect it with the lightning cable to the computer in which I assume is due to the bluetooth radio not being used and resorting to USB hardware/drivers (perhaps it's registered with a different USB device ID than the original Apple aluminium keyboard, I didn't verify). Doing so allows function key usage and all the tricks like function keys or multimedia keys by default that you find on the internet.

However, I would like to have the same features available when using it as a bluetooth keyboard. I would go as far as patching the kernel, but have no idea where to start and how to test and debug (obviously I would like to try out less "invasive" means first).

Any idea on how to address this problem are welcome.

Update

When I read from /dev/hidraw0, I get some activity when hitting the Fn key, so this could mean the fn keypress is registered by the system, but gets lost somewhere along the way…

Update2

evtest does not show any event when pressing the Fn key and /dev/input/event4 (which is the event device for the Magic Keyboard) does not trigger an event (other keys do). So I think the problem is that the Fn key gets read by the system (implied by /dev/hidraw0 showing data) but it doesn't get passed on to /dev/input/event4. But this is just speculation as I don't know how the flow of user input data is meant to be working in Linux.

Update 3

This is what several fn key presses (press+release) produce:

> sudo cat /dev/hidraw2 | hexdump
0000000 0001 0000 0000 0000 0000 0001 0000 0000
0000010 0000 0200 0001 0000 0000 0000 0000 0001
0000020 0000 0000 0000 0200 0001 0000 0000 0000
0000030 0000 0001 0000 0000 0000 0200 0001 0000
0000040 0000 0000 0000 0001 0000 0000 0000 0200
0000050 0001 0000 0000 0000 0000 0001 0000 0000
0000060 0000 0200 0001 0000 0000 0000 0000 0001
0000070 0000 0000 0000 0200 0001 0000 0000 0000
0000080 0000 0001 0000 0000 0000 0200 0001 0000
0000090 0000 0000 0000 0001 0000 0000 0000 0200
00000a0 0001 0000 0000 0000 0000 0001 0000 0000
00000b0 0000 0200 0001 0000 0000 0000 0000 0001
00000c0 0000 0000 0000 0200 0001 0000 0000 0000
00000d0 0000 0001 0000 0000 0000 0200 0001 0000
00000e0 0000 0000 0000 0001 0000 0000 0000 0200
00000f0 0001 0000 0000 0000 0000 0001 0000 0000
0000100 0000 0200 0001 0000 0000 0000 0000 0001
0000110 0000 0000 0000 0200 0001 0000 0000 0000
0000120 0000 0001 0000 0000 0000 0200 0001 0000
0000130 0000 0000 0000 0001 0000 0000 0000 0200
0000140 0001 0000 0000 0000 0000 0001 0000 0000
0000150 0000 0200 0001 0000 0000 0000 0000 0001
0000160 0000 0000 0000 0200 0001 0000 0000 0000
0000170 0000 0001 0000 0000 0000 0200 0001 0000
0000180 0000 0000 0000 0001 0000 0000 0000 0200
0000190 0001 0000 0000 0000 0000 0001 0000 0000
00001a0 0000 0200 0001 0000 0000 0000 0000 0001
00001b0 0000 0000 0000 0200 0001 0000 0000 0000
00001c0 0000 0001 0000 0000 0000 0200 0001 0000
00001d0 0000 0000 0000 0001 0000 0000 0000 0200
00001e0 0001 0000 0000 0000 0000 0001 0000 0000
00001f0 0000 0200 0001 0000 0000 0000 0000 0001
0000200 0000 0000 0000 0200 0001 0000 0000 0000
0000210 0000 0001 0000 0000 0000 0200 0001 0000
0000220 0000 0000 0000 0001 0000 0000 0000 0200
0000230 0001 0000 0000 0000 0000 0001 0000 0000

Weirdly enough, sometimes 2 lines but mostly 1 line is printed after releasing fn.

This is what F2 and Fn+F2 respectively look like:

sudo cat /dev/hidraw2 | hexdump
0000000 0001 0000 0000 0000 0000 0001 3b00 0000
^[OQ0000010 0000 0000 0001 0000 0000 0000 0000 0001
^[OQ0000020 3b00 0000 0000 0000 0001 0000 0000 0000
^[OQ0000030 0000 0001 3b00 0000 0000 0000 0001 0000
0000040 0000 0000 0000 0001 3b00 0000 0000 0000
^[OQ0000050 0001 0000 0000 0000 0000 0001 3b00 0000
^[OQ0000060 0000 0000 0001 0000 0000 0000 0000 0001
^[OQ0000070 3b00 0000 0000 0000 0001 0000 0000 0000
0000080 0000 0101 0000 0000 0000 0000 0101 0600
^C

Fn+F2:

> sudo cat /dev/hidraw2 | hexdump
0000000 0001 0000 0000 0000 0000 0001 0000 0000
^[OQ0000010 0000 0200 0001 3b00 0000 0000 0200 0001
0000020 0000 0000 0000 0200 0001 3b00 0000 0000
^[OQ0000030 0200 0001 0000 0000 0000 0200 0001 3b00
^[OQ0000040 0000 0000 0200 0001 0000 0000 0000 0200
^[OQ0000050 0001 3b00 0000 0000 0200 0001 0000 0000
^[OQ0000060 0000 0200 0001 3b00 0000 0000 0200 0001
0000070 0000 0000 0000 0200 0001 3b00 0000 0000
^[OQ0000080 0200 0001 0000 0000 0000 0200 0001 3b00
^[OQ0000090 0000 0000 0200 0001 0000 0000 0000 0200
^[OQ00000a0 0001 3b00 0000 0000 0200 0001 0000 0000
00000b0 0000 0200 0001 0000 0000 0000 0000 0101
00000c0 0000 0000 0000 0000 0101 0600 0000 0000
^C

Update 4

As requested from @dirkt, here's the report descriptor information (I couldn't run the line as per the comment, so here's the full dump; also note that it's now hidraw2 as I had to replace the keyboard):

> sudo ./hid-desc /dev/hidraw2
Report Descriptor Size: 171
Report Descriptor:
05 01 09 06 a1 01 85 01 05 07 15 00 25 01 19 e0 29 e7 75 01 95 08 81 02 95 05 75 01 05 08 19 01 29 05 91 02 95 01 75 03 91 03 95 08 75 01 15 00 25 01 06 00 ff 09 03 81 03 95 06 75 08 15 00 25 65 05 07 19 00 29 65 81 00 95 01 75 01 15 00 25 01 05 0c 09 b8 81 02 95 01 75 01 06 00 ff 09 03 81 02 95 01 75 06 81 03 06 02 ff 09 55 85 55 15 00 26 ff 00 75 08 95 40 b1 a2 c0 06 00 ff 09 14 a1 01 85 90 05 84 75 01 95 03 15 00 25 01 09 61 05 85 09 44 09 46 81 02 95 05 81 01 75 08 95 01 15 00 26 ff 00 09 65 81 02 c0 00 

Raw Name: Magic Keyboard
Raw Phys: 00:c2:c6:f7:eb:57
Raw Info:
    bustype: 5 (Bluetooth)
    vendor: 0x004c
    product: 0x0267

Best Answer

Partial answer: Making sense of the HID infrastructure and the HID raw data

(Disclaimer: I've only done all this for USB, but I suppose it will apply in the same or a similar way to Bluetooth).

HID devices can send and receive reports in a well-defined format. The format for a particular device is given by the HID descriptor, which for USB is very similar to the other USB descriptors (e.g. lsusb can list them if they are not bound). Details (for USB) can be found in the Device Class Definition for Human Interface Devices (HID) PDF document.

Kernel documentation for HID can be found Documentation/hid. As hiddev.txt explains, the dataflow for an event is like this:

 usb.c --> hid-core.c --> hid-input.c --> input-subsystem

In drivers/hid/hid-input.c, in particular in the routine hidinput_configure_usage, a report is parsed according to the HID descriptor.

So if you can't see the Fn key, that's where things go wrong.

The output seen at hidraw0 looks suspiciously like there are several kinds of reports with different IDs (this report has ID 1, normal keyboard reports have ID 0).

But to make sure, we need the HID descriptor(s). HID descriptors are available via an ioctl on the hidraw device. You can use for example https://github.com/DIGImend/usbhid-dump to get the descriptor (USB only), and https://github.com/DIGImend/hidrd to parse it. There's also /samples/hidraw/hid-example.c file in the kernel source that shows how to get the HID descriptor via the ioctl; it can be easily modified to produce an hex-dump similar to usbhid-dump. You'll have to use this for Bluetooth, so I put it in a pastebin. Compile with make.

(If you are not used to compiling external projects: Download zip file for both, unpack each into an empty directory, ./bootstrap, ./configure, make. Now you can use the binaries directly, add them $PATH, etc.)

Now you can parse the descriptor using

sudo ./hid-desc /dev/hidraw0 | tail -n+3 | head -1 | hidrd-convert -ihex -ospec

In addition to providing this output (or the hexdump, if anything doesn't work), please test what happens on hidraw if you press the Fn in combination with various other keys (alphabetic, arrows). Also test what happens for normal keypresses.

I'm not sure about the best way to proceed if it's not possible to make the kernel recognize the special reports. Maybe the simplest way is to write a C program that analyzes events from hidraw and produces additional input-events, similarly to input-create.

Update: The HID descriptor contains an extra 00 at the end. If you remove that, it parses to

Usage Page (Desktop),                           ; Generic desktop controls (01h)
Usage (Keyboard),                               ; Keyboard (06h, application collection)
Collection (Application),
    Report ID (1),                      ; +00 report id
    Usage Page (Keyboard),                      ; Keyboard/keypad (07h)
    Logical Minimum (0),
    Logical Maximum (1),
    Usage Minimum (KB Leftcontrol),             ; Keyboard left control (E0h, dynamic value)
    Usage Maximum (KB Right GUI),               ; Keyboard right GUI (E7h, dynamic value)
    Report Size (1),
    Report Count (8),
    Input (Variable),                   ; +01 modifier
    Report Count (5),
    Report Size (1),
    Usage Page (LED),                           ; LEDs (08h)
    Usage Minimum (01h),
    Usage Maximum (05h),
    Output (Variable),
    Report Count (1),
    Report Size (3),
    Output (Constant, Variable),
    Report Count (8),
    Report Size (1),
    Logical Minimum (0),
    Logical Maximum (1),
    Usage Page (FF00h),                         ; FF00h, vendor-defined
    Usage (03h),
    Input (Constant, Variable),         ; +02 vendor
    Report Count (6),
    Report Size (8),
    Logical Minimum (0),
    Logical Maximum (101),
    Usage Page (Keyboard),                      ; Keyboard/keypad (07h)
    Usage Minimum (None),                       ; No event (00h, selector)
    Usage Maximum (KB Application),             ; Keyboard Application (65h, selector)
    Input,                              ; +03 6 keysym bytes
    Report Count (1),
    Report Size (1),
    Logical Minimum (0),
    Logical Maximum (1),
    Usage Page (Consumer),                      ; Consumer (0Ch)
    Usage (Eject),                              ; Eject (B8h, one-shot control)
    Input (Variable),                   : +09.0
    Report Count (1),
    Report Size (1),
    Usage Page (FF00h),                         ; FF00h, vendor-defined
    Usage (03h),
    Input (Variable),                   ; +09.1
    Report Count (1),
    Report Size (6),
    Input (Constant, Variable),         : +09.2-7
    Usage Page (FF02h),                         ; FF02h, vendor-defined
    Usage (55h),
    Report ID (85),
    Logical Minimum (0),
    Logical Maximum (255),
    Report Size (8),
    Report Count (64),
    Feature (Variable, No Preferred, Volatile),
End Collection,
Usage Page (FF00h),                             ; FF00h, vendor-defined
Usage (14h),
Collection (Application),
    Report ID (144),
    Usage Page (Power Device),                  ; Power device (84h, power page)
    Report Size (1),
    Report Count (3),
    Logical Minimum (0),
    Logical Maximum (1),
    Usage (61h),
    Usage Page (Power Batsys),                  ; Power battery system (85h, power page)
    Usage (44h),
    Usage (46h),
    Input (Variable),
    Report Count (5),
    Input (Constant),
    Report Size (8),
    Report Count (1),
    Logical Minimum (0),
    Logical Maximum (255),
    Usage (65h),
    Input (Variable),
End Collection

There is one input event report with id hex 01, one battery status report with id hex 90, one output to set the LEDs as usual, and one vendor-specific feature control.

I marked the bytes for the input event report. There's several vendor defined field where we don't know what they do, and have to guess.

The input event report consists of 10 bytes, and your examples decode as follows:

ID MM VA K1 K2 K3 K4 K5 K6 VB

01 00 00 00 00 00 00 00 00 02  ; press? Fn 
01 00 00 00 00 00 00 00 00 00  ; release? Fn

01 00 00 3b 00 00 00 00 00 00  ; press F2
01 00 00 00 00 00 00 00 00 00  ; release

01 00 00 00 00 00 00 00 00 00  ;
01 00 00 00 00 00 00 00 00 02  ; press Fn?
01 00 00 3b 00 00 00 00 00 02  ; press F2
01 00 00 00 00 00 00 00 00 02  ; release F2 (but not Fn?)

ID is the report it. MM are the standard 8 modifier bits, which don't have room for the Fn key. K1 to K6 are up to 6 keys pressed simultanously. VA and VB are vendor specific. Assuming you held Fn and just pressed and released F2 in the last example, my guess is that bit 1 in VB represents the modifier for Fn (or at least something related to it).

Use hexdump -e '10/1 "%02X ""\n"' to get 9 bytes of output per line, and test this hypothesis by combining Fn with several keys, including those combinations you want to redefine in the end.

Update: For completeness and future reference, though I assume it's not relevant anymore for this particular case: It's possible to inject HID events using UHID, see Documentation/hid/uhid.txt and samples/uhid/uhid-example.c in the kernel.

Related Question