Skip to content

Instantly share code, notes, and snippets.

@fauxpark
Last active March 20, 2024 19:55
  • Star 98 You must be signed in to star a gist
  • Fork 14 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save fauxpark/010dcf5d6377c3a71ac98ce37414c6c4 to your computer and use it in GitHub Desktop.
QMK Apple Fn

QMK Apple Fn Key

This patch adds support for the Apple Fn key, which unlike most keyboards with Fn keys, is actually sent over the wire. It works by repurposing the reserved byte in the keyboard report to represent the KeyboardFn usage of the AppleVendor Top Case usage page. When the Fn key is pressed, the value of this byte becomes 1.

To apply this patch, download the below file, cd to your qmk_firmware repository in your preferred terminal, and run git apply /path/to/applefn.patch. Then, add the QK_APPLE_FN keycode (or AP_FN for short) to your keymap.

There are a couple of caveats to this implementation that are important to be aware of. Firstly, it is not compatible with NKRO, as QMK's NKRO report format has no reserved byte - it is part of the 6KRO report for compatibility with the HID boot protocol. Thus you must set NKRO_ENABLE = no in your keymap's rules.mk. You will also need to redefine the USB Vendor and Product IDs in your keymap's config.h to that of a genuine Apple keyboard* in order for macOS to recognise the Fn key:

#undef VENDOR_ID
#define VENDOR_ID 0x05AC
#undef PRODUCT_ID
#define PRODUCT_ID <pid>

This is the primary reason this patch has not been integrated into upstream QMK - Apple would probably not be too happy about others using their vendor ID, and a feature that relies on the VID/PID pair being set to a specific value is not particularly ideal anyway.

See qmk/qmk_firmware#2179 for a little more info and discussion.

* It appears that the functionality of certain F keys can differ depending on the PID, likely because they have evolved over time on real Apple keyboards.

diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk
index 18f8b0bbfc..4ef3e230e4 100644
--- a/builddefs/common_features.mk
+++ b/builddefs/common_features.mk
@@ -878,6 +878,10 @@ ifeq ($(strip $(JOYSTICK_ENABLE)), yes)
endif
endif
+ifeq ($(strip $(APPLE_FN_ENABLE)), yes)
+ OPT_DEFS += -DAPPLE_FN_ENABLE
+endif
+
USBPD_ENABLE ?= no
VALID_USBPD_DRIVER_TYPES = custom vendor
USBPD_DRIVER ?= vendor
diff --git a/data/constants/keycodes/keycodes_0.0.2_applefn.hjson b/data/constants/keycodes/keycodes_0.0.2_applefn.hjson
new file mode 100644
index 0000000000..1378413a9e
--- /dev/null
+++ b/data/constants/keycodes/keycodes_0.0.2_applefn.hjson
@@ -0,0 +1,11 @@
+{
+ "keycodes": {
+ "0x5300": {
+ "group": "apple_fn",
+ "key": "QK_APPLE_FN",
+ "aliases": [
+ "AP_FN"
+ ]
+ }
+ }
+}
diff --git a/quantum/action.c b/quantum/action.c
index 6368f7398c..d9bd34f681 100644
--- a/quantum/action.c
+++ b/quantum/action.c
@@ -555,6 +555,18 @@ void process_action(keyrecord_t *record, action_t action) {
}
break;
#endif // EXTRAKEY_ENABLE
+#ifdef APPLE_FN_ENABLE
+ /* Apple Fn */
+ case ACT_APPLE_FN:
+ if (event.pressed) {
+ add_apple_fn(keyboard_report);
+ send_keyboard_report();
+ } else {
+ del_apple_fn(keyboard_report);
+ send_keyboard_report();
+ }
+ break;
+#endif
/* Mouse key */
case ACT_MOUSEKEY:
register_mouse(action.key.code, event.pressed);
@@ -1196,6 +1208,9 @@ void debug_action(action_t action) {
case ACT_USAGE:
ac_dprintf("ACT_USAGE");
break;
+ case ACT_APPLE_FN:
+ dprint("ACT_APPLE_FN");
+ break;
case ACT_MOUSEKEY:
ac_dprintf("ACT_MOUSEKEY");
break;
diff --git a/quantum/action_code.h b/quantum/action_code.h
index d9a575b518..f347010c8d 100644
--- a/quantum/action_code.h
+++ b/quantum/action_code.h
@@ -53,7 +53,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
* ACT_SWAP_HANDS(0110):
* 0110|xxxx| keycode Swap hands (keycode on tap, or options)
*
- * 0111|xxxx xxxx xxxx (reserved)
+ * ACT_APPLE_FN(0111):
+ * 0111|0000|0000|0000 Apple Fn
*
* Layer Actions(10xx)
* -------------------
@@ -95,6 +96,8 @@ enum action_kind_id {
ACT_MOUSEKEY = 0b0101,
/* One-hand Support */
ACT_SWAP_HANDS = 0b0110,
+ /* Apple Fn */
+ ACT_APPLE_FN = 0b0111,
/* Layer Actions */
ACT_LAYER = 0b1000,
ACT_LAYER_MODS = 0b1001,
@@ -182,6 +185,7 @@ enum usage_pages {
#define ACTION_USAGE_SYSTEM(id) ACTION(ACT_USAGE, PAGE_SYSTEM << 10 | (id))
#define ACTION_USAGE_CONSUMER(id) ACTION(ACT_USAGE, PAGE_CONSUMER << 10 | (id))
+#define ACTION_APPLE_FN() ACTION(ACT_APPLE_FN, 0)
#define ACTION_MOUSEKEY(key) ACTION(ACT_MOUSEKEY, key)
/** \brief Layer Actions
diff --git a/quantum/keycodes.h b/quantum/keycodes.h
index bbf10da36d..b8e894a399 100644
--- a/quantum/keycodes.h
+++ b/quantum/keycodes.h
@@ -310,6 +310,7 @@ enum qk_keycode_defines {
KC_RIGHT_SHIFT = 0x00E5,
KC_RIGHT_ALT = 0x00E6,
KC_RIGHT_GUI = 0x00E7,
+ QK_APPLE_FN = 0x5300,
QK_SWAP_HANDS_TOGGLE = 0x56F0,
QK_SWAP_HANDS_TAP_TOGGLE = 0x56F1,
QK_SWAP_HANDS_MOMENTARY_ON = 0x56F2,
@@ -938,6 +939,7 @@ enum qk_keycode_defines {
KC_RGUI = KC_RIGHT_GUI,
KC_RCMD = KC_RIGHT_GUI,
KC_RWIN = KC_RIGHT_GUI,
+ AP_FN = QK_APPLE_FN,
SH_TOGG = QK_SWAP_HANDS_TOGGLE,
SH_TT = QK_SWAP_HANDS_TAP_TOGGLE,
SH_MON = QK_SWAP_HANDS_MOMENTARY_ON,
@@ -1406,6 +1408,7 @@ enum qk_keycode_defines {
#define IS_CONSUMER_KEYCODE(code) ((code) >= KC_AUDIO_MUTE && (code) <= KC_LAUNCHPAD)
#define IS_MOUSE_KEYCODE(code) ((code) >= KC_MS_UP && (code) <= KC_MS_ACCEL2)
#define IS_MODIFIER_KEYCODE(code) ((code) >= KC_LEFT_CTRL && (code) <= KC_RIGHT_GUI)
+#define IS_APPLE_FN_KEYCODE(code) ((code) >= QK_APPLE_FN && (code) <= QK_APPLE_FN)
#define IS_SWAP_HANDS_KEYCODE(code) ((code) >= QK_SWAP_HANDS_TOGGLE && (code) <= QK_SWAP_HANDS_ONE_SHOT)
#define IS_MAGIC_KEYCODE(code) ((code) >= QK_MAGIC_SWAP_CONTROL_CAPS_LOCK && (code) <= QK_MAGIC_TOGGLE_ESCAPE_CAPS_LOCK)
#define IS_MIDI_KEYCODE(code) ((code) >= QK_MIDI_ON && (code) <= QK_MIDI_PITCH_BEND_UP)
diff --git a/quantum/keymap_common.c b/quantum/keymap_common.c
index 9a67fad278..36a0b309be 100644
--- a/quantum/keymap_common.c
+++ b/quantum/keymap_common.c
@@ -70,6 +70,11 @@ action_t action_for_keycode(uint16_t keycode) {
case KC_AUDIO_MUTE ... KC_LAUNCHPAD:
action.code = ACTION_USAGE_CONSUMER(KEYCODE2CONSUMER(keycode));
break;
+#endif
+#ifdef APPLE_FN_ENABLE
+ case QK_APPLE_FN:
+ action.code = ACTION_APPLE_FN();
+ break;
#endif
case KC_MS_UP ... KC_MS_ACCEL2:
action.code = ACTION_MOUSEKEY(keycode);
diff --git a/tmk_core/protocol/report.c b/tmk_core/protocol/report.c
index 1ba3be4604..b53d3f687b 100644
--- a/tmk_core/protocol/report.c
+++ b/tmk_core/protocol/report.c
@@ -299,3 +299,13 @@ __attribute__((weak)) bool has_mouse_report_changed(report_mouse_t* new_report,
return changed;
}
#endif
+
+#ifdef APPLE_FN_ENABLE
+void add_apple_fn(report_keyboard_t* keyboard_report) {
+ keyboard_report->reserved = 1;
+}
+
+void del_apple_fn(report_keyboard_t* keyboard_report) {
+ keyboard_report->reserved = 0;
+}
+#endif
diff --git a/tmk_core/protocol/report.h b/tmk_core/protocol/report.h
index 9d415a3bfd..8c65b40386 100644
--- a/tmk_core/protocol/report.h
+++ b/tmk_core/protocol/report.h
@@ -348,6 +348,11 @@ void clear_keys_from_report(report_keyboard_t* keyboard_report);
bool has_mouse_report_changed(report_mouse_t* new_report, report_mouse_t* old_report);
#endif
+#ifdef APPLE_FN_ENABLE
+void add_apple_fn(report_keyboard_t* keyboard_report);
+void del_apple_fn(report_keyboard_t* keyboard_report);
+#endif
+
#ifdef __cplusplus
}
#endif
diff --git a/tmk_core/protocol/usb_descriptor.c b/tmk_core/protocol/usb_descriptor.c
index e215c90900..e38c0d37f7 100644
--- a/tmk_core/protocol/usb_descriptor.c
+++ b/tmk_core/protocol/usb_descriptor.c
@@ -75,10 +75,22 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM KeyboardReport[] = {
HID_RI_REPORT_COUNT(8, 0x08),
HID_RI_REPORT_SIZE(8, 0x01),
HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
+
+#ifdef APPLE_FN_ENABLE
+ HID_RI_USAGE_PAGE(8, 0xFF), // AppleVendor Top Case
+ HID_RI_USAGE(8, 0x03), // KeyboardFn
+ HID_RI_LOGICAL_MINIMUM(8, 0x00),
+ HID_RI_LOGICAL_MAXIMUM(8, 0x01),
+ HID_RI_REPORT_COUNT(8, 0x01),
+ HID_RI_REPORT_SIZE(8, 0x08),
+ HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
+#else
// Reserved (1 byte)
HID_RI_REPORT_COUNT(8, 0x01),
HID_RI_REPORT_SIZE(8, 0x08),
HID_RI_INPUT(8, HID_IOF_CONSTANT),
+#endif
+
// Keycodes (6 bytes)
HID_RI_USAGE_PAGE(8, 0x07), // Keyboard/Keypad
HID_RI_USAGE_MINIMUM(8, 0x00),
diff --git a/tmk_core/protocol/vusb/vusb.c b/tmk_core/protocol/vusb/vusb.c
index d74f375f66..2ade1350ad 100644
--- a/tmk_core/protocol/vusb/vusb.c
+++ b/tmk_core/protocol/vusb/vusb.c
@@ -417,10 +417,22 @@ const PROGMEM uchar keyboard_hid_report[] = {
0x95, 0x08, // Report Count (8)
0x75, 0x01, // Report Size (1)
0x81, 0x02, // Input (Data, Variable, Absolute)
+
+#ifdef APPLE_FN_ENABLE
+ 0x05, 0xFF, // Usage Page (AppleVendor Top Case)
+ 0x09, 0x03, // Usage (KeyboardFn)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x95, 0x01, // Report Count (1)
+ 0x75, 0x08, // Report Size (8)
+ 0x81, 0x02, // Input (Data, Variable, Absolute)
+#else
// Reserved (1 byte)
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x03, // Input (Constant)
+#endif
+
// Keycodes (6 bytes)
0x05, 0x07, // Usage Page (Keyboard/Keypad)
0x19, 0x00, // Usage Minimum (0)
@nitsujri
Copy link

nitsujri commented Dec 5, 2022

@fauxpark thanks so much for this patch! Looks like there were some big breaking changes in quantum/ recently.

So much so that I can't figure out how to apply this patch manually even. Possible to get help with this?

@nitsujri
Copy link

nitsujri commented Dec 9, 2022

@fauxpark omg you're amazing! Thanks for updating this!!!

@chrismanderson
Copy link

@jhorology Do you still have the patch to enable NKRO? Your link no longer works, thanks!

@marceliwac
Copy link

marceliwac commented Feb 23, 2023

It's probably worth mentioning that QMK has moved from defining VENDOR_ID and PRODUCT_ID explicitly to an info.json file now and deprecated the previous behaviour. Skipping the step where you define VENDOR_ID and PRODUCT_ID and updating keyboards/<keyboard>/info.json works fine.

Here's an excerpt from my working version (planck/rev6_drop):

  // ...
  "usb": {
    "vid": "0x05AC",
    "pid": "0x0267",
    "device_version": "0.0.6"
  },
  // ...

@fauxpark
Copy link
Author

That's not necessary, you can still override the keyboard-level VID/PID with the defines in your keymap-level config.h (in fact this is what you are meant to do anyway; you shouldn't modify keyboard-level code).

@marceliwac
Copy link

@fauxpark You're right, I was mistakingly changing it in keyboard-level config.h and not keymap-level config.h. Thanks for clarifying this!

@rodolfo-gonzalez
Copy link

@jhorology i used your patch and i was able to use apple fn while usb but i found that the bluetooth part is a TODO here https://github.com/jhorology/keyboards/blob/d95766b6f5f1a99de6acbe750462fdd3af26d43a/patches/qmk_00_applefn.patch#L29

what has to be done to make this work over bluetooth?

any help is appreciated thank you

@fauxpark
Copy link
Author

It's not possible; the Bluetooth modules QMK supports are separate devices and the report descriptors cannot be changed.

@jhorology
Copy link

ZMK works as a charm with both USB and wirless.
patch is here
https://github.com/jhorology/zmk-config-ckp/blob/bt60-hhkb-ec11/patches/zmk_apple_fn.patch

@nitsujri
Copy link

@fauxpark thanks again for keep this up to date! I recently ran it against master and updated all my keebs.

For those who are updating (not coming fresh) - I recommend:

  1. Copying your current keymap
  2. Going back to master to apply the patch.
  3. Update all your keymaps.c to QK_APPLE_FN.

@Tianle-Leviathan
Copy link

It's not possible; the Bluetooth modules QMK supports are separate devices and the report descriptors cannot be changed.

Thanks again for providing this patch. I am new to qmk and not familiar with bluetooth. If I understand correctly, in the patch, you update the HID report descriptor so that the reserved byte is interpreted as apple fn but when it comes to bluetooth, there is no way to do the same thing and this interpretation cannot be done.
I am thinking update the report_keyboard_t to something apple is expecting

#define KEYBOARD_REPORT_KEYS 5
typedef union {
    uint8_t raw[KEYBOARD_REPORT_SIZE];
    struct {
        uint8_t mods;
        uint8_t reserved;
        uint8_t keys[KEYBOARD_REPORT_KEYS];
        uint8_t fn;
    }
} report_keyboard_t;

This would definitely take up one keycode. Do you think this is going to work? Thanks

@fauxpark
Copy link
Author

fauxpark commented Sep 9, 2023

No, it won't work, for the same reason: the report descriptor on the Bluetooth module dictates what the format and structure of the report will be.

@Tianle-Leviathan
Copy link

Tianle-Leviathan commented Sep 9, 2023

No, it won't work, for the same reason: the report descriptor on the Bluetooth module dictates what the format and structure of the report will be.

Thanks

@johnzoki
Copy link

I got these two errors when trying to apply :/

error: patch failed: tmk_core/protocol/report.c:291
error: tmk_core/protocol/report.c: patch does not apply

@HVR88
Copy link

HVR88 commented Oct 11, 2023

How does one enable VIA to recognize the keyboard once it's using Apple's VID/PID?

@fauxpark
Copy link
Author

You would need to use a custom VIA JSON file for your board. This is kind of the problem with both how VIA identifies devices, and with this patch.

@HVR88
Copy link

HVR88 commented Oct 11, 2023

Sorted, thank you. The JSON I was using last night was malformed and after downloading the current one from Keychron's github I was able to make it work.

Have you seen this yet?

https://developer.apple.com/accessories/Accessory-Design-Guidelines.pdf

Specifically Table 13-3 HID Consumer Page (0x0C) controls for use by keyboards

image

@HVR88
Copy link

HVR88 commented Oct 11, 2023

Using the above consumer code, I can get the fn/globe key to work as a single key (to bring up the emoji picker for example) but I don't know how to get it to work as a modifier. I've used process_record_user in the keymap to send the consumer code.

It looks like they have this working in ZMK: zmkfirmware/zmk#947 - at least the same way as mapping CAPS to Globe works from the OS (it works as a modifier this way)

@fauxpark
Copy link
Author

fauxpark commented Oct 12, 2023

Just tested it myself, and yeah, it seems to work on its own (the Accessibility keyboard even changes the F-key legends when pressed), but the same thing is happening - the key won't be registered as a proper Fn key unless you use an Apple VID/PID pair. So unfortunately this new(?) usage doesn't help much, however it does make this patch basically obsolete - you can simply make a new custom keycode like so:

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
    switch (keycode) {
        case AP_GLOB:
            if (record->event.pressed) {
                host_consumer_send(AC_NEXT_KEYBOARD_LAYOUT_SELECT);
            } else {
                host_consumer_send(0);
            }
            return false;
    }
    return true;
}

And redefine the VID & PID as previous.

@marceliwac
Copy link

@fauxpark Am I correct to reason that if this is the case, it removes the NKRO limitations previously imposed by this patch? I'm mostly asking because if the VID/PID constraints are the only ones remaining it might be feasible to think about integrating into the QMK upstream. Of course, it would make the fn key work out of the box but it would certainly make things easier for what at this point seems to be a community of people who use this functionality. Again, thanks for continued work and testing of this patch!

@fauxpark
Copy link
Author

I would assume this plays well with NKRO, since it's now just a matter of sending a Consumer usage like the other media keys. That also means there's nothing really to upstream, but we still will not accept keyboards or keymap PRs using Apple VID/PID pairs for this purpose.

@fauxpark
Copy link
Author

I've opened qmk/qmk_firmware#22256 to add this usage to the enum in report.h - however there is no keycode (yet).

@HVR88
Copy link

HVR88 commented Oct 12, 2023

Just tested it myself, and yeah, it seems to work on its own (the Accessibility keyboard even changes the F-key legends when pressed), but the same thing is happening - the key won't be registered as a proper Fn key unless you use an Apple VID/PID pair

I have a few more tests to run, as I have an idea for why we're seeing the different behavior and it's not due to VID/PID (I'm like 80% sure anyway). If I'm right, the solution will need a bit more code change besides adding an enum for the code.

I also have little desire to change the VID/PID on my own board, which is why I've been hunting for an alternative solution. Outside of this specific consumer code, the best I've come up with is to use CAPS code and redefine CAPS at the OS level to be Globe, then create my own CAPS_LOCK using AUTO_SHIFT. It works well, but that's a lot that someone else would have to do manually.

@HVR88
Copy link

HVR88 commented Oct 12, 2023

OK, ran some preliminary tests I can now share.

My first suspicion was that the key was being sent as some kind of one-shot with both down+up events every time it's pressed. This isn't the case as I've verified with Key Codes (a key code viewer from Many Tricks) in MacOS (Sonoma) that it's recognized as a modifier and only the down even is processed when held, with the up coming on release - same as the Globe/Fn on an internal keyboard.

The issue however, is that it's not behaving correctly when rolling over any other key. Specifically, it isn't acting as a modifier when performing a combination with other keys

When rolling another key using the built-in Globe/Fn, that other key doesn't show up in Key Codes, it just produces the intended function. Example Fn-N shows/hides the Notification Center

When rolling the QMK consumer code version, the other key passes through both down and up events and has no special functional effect - it just prints its normal letter for example.

This is not the behavior the folks responsible for ZMK are seeing as documented here:

zmkfirmware/zmk#1938 (comment)

In the link above, the poster Seth clearly states it's working like the built-in keyboard for many rollover combos including N, F, H, etc.

Further, he also states that "Changing the VID and PID of the ZMK keyboard to match ... Apple ... did not affect any of this. "

And also "Remapping Option key on the ZMK keyboard to Globe in System Settings, Option behaves the same as the ZMK GLOBE key, i.e. works with basic default bindings, but not as a modifier for backspace or arrow keys."

Neither of those are the case using this simple send consumer code approach in QMK.

My knowledge of QMK is about as basic as it gets, but I hope I've been able to source enough information that someone familiar with its internal workings can reference this material and figure out how to match the functionality seen in ZMK.

@fauxpark
Copy link
Author

fauxpark commented Oct 12, 2023

Further, he also states that "Changing the VID and PID of the ZMK keyboard to match ... Apple ... did not affect any of this. "

This is incorrect. Changing the VID/PID does allow the F-keys to behave as they would on a real Apple keyboard. Without it, they are always just F1-12.
This appears to be the extent of the functionality in ZMK - there is nothing special it is doing, nor can be done. You need to use an Apple VID/PID for it to work properly. Note that the document linked above makes absolutely no mention of Fn. It is only the Globe key. I believe this indicates that Apple does not intend for third-party keyboards to have a Fn key.

@HVR88
Copy link

HVR88 commented Oct 12, 2023

This appears to be the extent of the functionality in ZMK - there is nothing special it is doing

Globe and Fn are one and the same on Apple boards.

Have you tried the combination keys? Have you tried the built-in MacOS method of using CTRL and Globe/Fn?

Here with QMK on my Keychron Q3 I can't press Globe+N to bring up Notifications. This works on ZMK. This works using MacOS CTRL-to-Globe/Fn remap.

The thread I linked to very specifically outlines that their firmware is working with the key the same way that Apple's remap works. QMK does not.

With respect to usage with F1-F12 I can only surmise that depends entirely on how those keys are mapped/assigned in the layout. Keychron's default layout uses two layers for this, with the base providing the special functions and layer one the F1-12 - activated by their own FN key which is mapped to MO(1) to activate layer 1. This obviously isn't how Apple's own keyboards do it, but it provides the functionality needed.

@HVR88
Copy link

HVR88 commented Oct 12, 2023

I've verified what was written on the ZMK link. Changing the VID/PID while using the consumer page Globe code has no effect. The behavior is exactly the same as when using any other VID/PID such as Keychron.

Combos are not possible simply by making this change on QMK. Adding this key code to QMK will offer only one function and that's the ability to pop-up the Emoji/character picker. It won't offer Globe/Fn as a modifier. Users are best off using either CAPS remapping or the patch which is the subject of this gist.

@fauxpark
Copy link
Author

fauxpark commented Oct 12, 2023

Globe and Fn are one and the same on Apple boards.

Yes, this is my point. They are evidently not the same on third-party boards.

The thread I linked to very specifically outlines that their firmware is working with the key the same way that Apple's remap works. QMK does not.

The documentation that was added (see the very last linked PR in that thread) states:

Does not exactly replicate original key behavior on macOS, works for Globe+key modifiers but not Fn+key

Which tells me that the functionality is the same that I got from my code above on QMK. This makes perfect sense to me, since, as far as I can tell, the code that was added to ZMK does essentially the same thing - it sends the 0x29D Consumer usage.

With respect to usage with F1-F12 I can only surmise that depends entirely on how those keys are mapped/assigned in the layout.

It has nothing to do with the keymap. It's as simple as: Fn+F1 (with the "Function Keys" toggle switched on) should turn the brightness down, etc. It does not; it only worked once I reflashed the board with an Apple VID/PID.

@HVR88
Copy link

HVR88 commented Oct 12, 2023

We obviously have access to the same links but we're seeing different results and making different interpretations.

The functionality using 0x29D between ZMK and QMK is not the same. I don't know what ZMK are doing on the back end, but the result is different. On QMK that code simply never works as a modifier - it cannot be combined with any other key, whether you're using an Apple VID/PID or not. With ZMK it DOES work - and Apple VID/PID has no change (it's not needed).

the code that was added to ZMK does essentially the same thing - it sends the 0x29D Consumer usage

But how does it send any other key while that key is being held? This is where the difference in behavior lies.

On QMK the only thing that works with modifiers is to use your original patch with VID/PID change or to use CAPS remapping. But see below - neither of these works with F1-12 like they do on a real Apple keyboard.

Absolutely F1-12 has to do with the keymap because the physical position of those keys on an MCU-powered keyboard has no meaning on what codes they produce, that's entirely defined by the keymap for qmk. So for instance as I mentioned, on my Keychron, those keys aren't set up with F-codes at all, so they will never work with any implementation of Globe/Fn - not 0x29D and not your patch.

Further, in my testing on this Q3, changing the F keys to F1-12 using VIA will still not let them do anything special when combined with Globe/Fn - neither the 0x29D nor your patch and not even with CAPS remapping (even using Apple VID/PID). Example: if I change the F4 key to send an actual F4, it does the plain F4 with or without the Globe/Fn. On a real Apple keyboard this works as expected an pressing the Globe/Fn does the special LAUNCH PAD feature.

If you're not seeing the behavior I'm describing above, then that's interesting in its own right.

@HVR88
Copy link

HVR88 commented Oct 12, 2023

Sorry for the walls of text.

It sounds to me like you're seeing the same behavior from your original patch and using 0x29d when changing VID/PID.

I'm not.

The documentation that was added (see the very last linked PR in that thread) states:

Does not exactly replicate original key behavior on macOS, works for Globe+key modifiers but not Fn+key

F-keys never work like an Apple keyboard on this Keychron Q3 with QMK regardless of testing method - M1 Air, Mac OS Sonoma.

@marceliwac
Copy link

@HVR88 I'm not sure if this is of any help, but I've been following this thread today, and I thought I'd chip in since it might shed some light on the different behaviour you might be seeing.

I'm using Drop Planck rev6 with this patch applied and mapping one of my keys to the Apple [fn] key. My 2019 13" Macbook Pro (4 Thunderbolt ports with touchbar) sees the [fn] key as a modifier just fine. I can verify this by pressing the [fn] key on its own, which, when held, toggles the media keys displayed on the touch bar and switches them to [f1]-[f12] keys. Other combinations work too (e.g. [fn] + [n] for notifications). I'm happy to do some testing of my own if that would help at all.

Interestingly, there isn't a globe icon on my Macbook's [fn]. I don't know to what extent this matters and whether it supports @fauxpark 's point of view. Maybe the separate signal sent by this patch's implementation does something different after all?

@HVR88
Copy link

HVR88 commented Oct 12, 2023

I'm using Drop Planck rev6 with this patch applied and mapping one of my keys to the Apple [fn] key. My 2019 13" Macbook Pro (4 Thunderbolt ports with touchbar) sees the [fn] key as a modifier just fine

For the most part here as well. This patch (requiring VID/PID change) is thusfar the most complete solution. I don't believe the lacking behavior with F1-12 on my system has anything to do with the patch per-se, just the way the Keychron/QMK must work.

I have M1 Macbook Air with real function keys and the Globe/Fn silkscreen, plus a wireless Apple Keyboard model A1255 from around 2007 with only the Fn silkscreen on the same key. QMK is a fresh pull from master and installed on a Keychron Q3 (USB)

When I brought up the consumer page link it was to give another avenue to investigate, since the ZMK ppl had luck with it, including use as a modifier.

When I tested it with QMK, though it works as a single key which is seen as Globe/Fn by the OS and other apps, it doesn't work as a modifier - I can't combine it with anything.

So this is the point I was making above, is that using the consumer 0x29d key is not (at least on my system and in my testing) the same or as functional as the original patch. It is also not the same or as functional as using the CAPS remap in the OS.

@drashna
Copy link

drashna commented Oct 12, 2023

FWIW, I've also been messing with the consumer page. I can verify that stuff like Globe + N do appear to work. However, the globe key only works on newer OS versions.

For reference: drashna/qmk_firmware@fb94bc4

@HVR88
Copy link

HVR88 commented Oct 12, 2023

FWIW, I've also been messing with the consumer page. I can verify that stuff like Globe + N do appear to work

Ok, I checked your link and immediately saw this:

"Just Extrakey enabled"

As I've been experimenting off the Keychron Q3 code in Master, this doesn't appear anywhere in any of the config files. Anything else enabled on your (or everyone else's) rules that I should try setting up?

Should there be any difference from how @drashna and @fauxpark have added this to core versus doing it in the keymap with process_record and host_consumer_send(0x29D); ? I can't think of why else I'm not seeing the behavior you have both described.

I've cloned drashna/qmk_firmware and the same behavior persists on this kb using KC_GLB - I can't combine it with additional keys like Globe + N

The difference between the behavior of this key on my keyboard boils down to this simple test:

Press and hold Globe Key for >1 second and then release - when it's working correctly, NOTHING should happen.

On my Q3/qmk holding for any amount of time will always bring up the Emoji picker when the key is released.

:(

@drashna
Copy link

drashna commented Oct 12, 2023

To clarify, karabiner does pick up the event. globe+n works, globe+e works, globe+h, and some of the listed behavior doesn't seem to work for me. (with and without karabiner installed)
I tested on several different boards (STM32F411, STM32F303, and RP2040). and seemed to work.

The main different between my implementation and fauxpark's PR is that I've added the code to support it as a keycode

@marceliwac
Copy link

@drashna Did you observe any changes in this behaviour with PID/VID changes? I'm aware you have mentioned this wasn't necessary, but I'm curious to know if that's the limiting factor for the globe+h functionality you described.

@HVR88
Copy link

HVR88 commented Oct 13, 2023

I just took a virtual axe to all the keychron-specific files in the source tree and removed every bit of cruft and keychron logic/functions I could see.

Issues mentioned previously persist. Really at a loss why others are seeing different behavior. Unfortunately this is the only QMK compatible board I own so I can't test another.

@fauxpark
Copy link
Author

So for reference, here is my entire setup.

  • I'm testing on a GH60 Satan, with the custom keycode above, replacing left control. My F-keys are on a second layer but this is largely irrelevant.
  • My repo is up to date with current master.
  • I have an M1 Pro MBP 14" 2021 (MacbookPro18,3).

Without an Apple VID/PID:
Usage 0x29D is recognised by macOS as Fn (seen in QMK Toolbox key tester), but does not respond to any combinations (Globe+N, Globe+F1-12)

Now I add the following to my keymap's config.h:

#undef VENDOR_ID
#undef PRODUCT_ID
#define VENDOR_ID  0x05AC // Apple
#define PRODUCT_ID 0x0220 // Aluminum Keyboard (ANSI)

With this change, the F-keys now operate as they should (brightness etc), although the functionality is slightly different as it is determined by the PID - F4 triggers Launchpad instead of Spotlight as the builtin keyboard does, for example. Globe+N still does not work.

I had a suspicion last night after looking at ZMK's report descriptors. I commented out the VID/PID override and added the following to my keymap's rules.mk:

KEYBOARD_SHARED_EP = yes

By default the keyboard and extrakeys (media+power) reports are sent to separate endpoints. As you might know, macOS treats each keyboard (possibly each HID device) as a separate entity, even if they are part of the same physical device. If you press Caps Lock on one keyboard, only that keyboard's LED will light, and not any others attached to the computer (as a side note this makes Caps Lock state difficult to sync when switching between 6KRO and NKRO on a Mac).

The above flag merges the keyboard report descriptor into the "shared" endpoint where the NKRO and extrakeys descriptors are. Now Globe+N works...but the F-keys do not.

One last try with this and the VID/PID override... surprisingly, no change. The Aluminum Keyboard is pre-Globe key, so it would be interesting to see if the pair from a newer keyboard makes a difference, though I don't actually know whether they can be used wired anymore or if they are purely Bluetooth these days.

@drashna
Copy link

drashna commented Oct 13, 2023

Ah, that makes a lot of sense. I do have shared EP enabled, since some of my boards are blackpills, which have a very low number of endpoints available.

@HVR88
Copy link

HVR88 commented Oct 13, 2023

Without an Apple VID/PID:
Usage 0x29D is recognised by macOS as Fn (seen in QMK Toolbox key tester), but does not respond to any combinations (Globe+N, Globe+F1-12)

OK, that now makes sense - which is what I've been seeing. The exception being that on my MBA M1, the key is recognized as Globe in System Settings. But I'm confident that makes no difference as other tools like Key Codes just call it Fn.

KEYBOARD_SHARED_EP = yes

So now we're getting somewhere. I hope this is the silver bullet, because at this point I've ripped everything out of the code that makes this Keychron a Keychron and still no joy.

The Aluminum Keyboard is pre-Globe key, so it would be interesting to see if the pair from a newer keyboard makes a difference

I was this close || yesterday to going out and buying a corded Apple keyboard from FB marketplace to run some tests.

Going to restore all the source files, make that rule addition and try this all again.

@HVR88
Copy link

HVR88 commented Oct 13, 2023

I've just tested with @drashna's patch. That was it, SHARED_EP gets me the same behavior you've seen. Globe/Fn + OTHER_KEYS works as mostly as expected. With F1-12 not working and Globe/Fn + Backspace not performing a DEL. This is also how this was described in the ZMK threads.

As we stand now, the only thing different in my setup seems to be that without the SHARED_EP and with Apple VID/PID, my F1-12 still never work the same as with an Apple keyboard using the Globe key. This is with those keys defined as simple F1-12 in the keymap.

Apart from some keys not responding in COMBO the same way as a real Apple keyboard, the other difference of note is something I mentioned previously. Pressing + holding and then releasing Globe/Fn still brings up the Emoji picker - on a real Apple board, the act of holding cancels the action and nothing should happen.

Interestingly, they both look exactly the same in Key Code viewer:

image

@Boiethios
Copy link

Hello, I'm writing my own keyboard firmware, and I think I did all correctly, but I cannot emulate the "globe key"/"fn key". What I tried:

  • I use the Apple vendor ID: 0x05AC
  • I my report descriptor, I replaced the part for the reserved byte with this one:
    0x05, 0xFF,         //   Usage Page (AppleVendor Top Case)
    0x09, 0x03,         //   Usage (KeyboardFn)
    0x15, 0x00,         //   Logical Minimum (0)
    0x25, 0x01,         //   Logical Maximum (1)
    0x95, 0x01,         //   Report Count (1)
    0x75, 0x08,         //   Report Size (8)
    0x81, 0x02,         //   Input (Data, Variable, Absolute)
    
  • When I press the fake fn key, I set the reserved byte to 1. For example, if I press it only, it looks like 00 01 00 00 00 00 00 00. I tested with a HID debugging tool, this report is sent when I press the key.

I'm lost at that moment. I wonder what I forgot to do to make it work.

@fauxpark
Copy link
Author

Just the VID is not enough, you also need to use a PID of an Apple keyboard with a Fn key.

@HVR88
Copy link

HVR88 commented Oct 15, 2023

Is there a special procedure to be able to use a custom key code inside LT()?

When trying to use the globe key within LT(1,KC_GLB) for example, it gets replaced with KC_F7 after compiling.

@fauxpark
Copy link
Author

fauxpark commented Oct 16, 2023

Layer-Tap and Mod-Tap only support basic keycodes.
https://docs.qmk.fm/#/keycodes

@HVR88
Copy link

HVR88 commented Oct 16, 2023

Right, I recall reading that, but some of the basic keys are consumer page, so I assumed incorrectly it should work. Is it a case of it only working with codes that are defined within qmk's own code then?

@fauxpark
Copy link
Author

The usage page is irrelevant. Only basic keycodes are supported.

@HVR88
Copy link

HVR88 commented Oct 16, 2023

The usage page is irrelevant. Only basic keycodes are supported.

I understand, this is what I was asking. Would it work if the KC_GLB were added to qmk as a basic keycode? (spoiler, it does). Which is what @drashna's change does.

I had been compiling from Master, but just switched over to @drashna's branch and can confirm it works as expected.

Using layers is the only way I can access clean function keys, so combining Globe with a layer tap I'm able to use one key for both. The caveat is that I have to include all the Globe modifier shortcuts into a layer instead of letting the OS handle it alone.

@HVR88
Copy link

HVR88 commented Oct 18, 2023

Decided to take another look at the whole F-key thing using Apple VID/PID and ran through the 4 PIDs I was able to find for Apple external keyboards with FN/Globe keys - mainly because I wasn't previously able to get the F-keys to produce their special functions. SPOILER: PID 0x0267 won't work on my system.

  • Using @drashna's code base with the consumer code for the Globe key.
  • Testing on M1 Macbook Air 2020 (first release), running macOS 14 (Sonoma - latest Beta 14.1)
  • this Macbook Air has a microphone icon on the F4 key of its built-in keyboard which brings up Spotlight

I'm not specifying any version string along with VID/PID Maybe that makes a difference to some of this.

QMK on Keychron Q3 - disabled Keychron's key processing hackery

Keychron VID/PID - no special functions on F-Keys. They come up as regular F-Keys

Apple 0x0267 (Magic Keyboard ANSI) - same as above - doesn't work with any special functions

Apple 0x0220 (Aluminum Keyboard ANSI) - All F-keys produce their special functions

  • F1/F2 Brightness work on Internal and External display - (internal only when external not connected)
  • Globe + F-Key does not produce a regular F-Key - it just continues to do the special function
  • Globe + N shows notification center
  • Globe + H shows desktop
  • Globe + E shows emoji picker
  • F4 brings up LaunchPad (not spotlight like internal keyboard)

Apple 0x024f (Aluminum Keyboard ANSI) - Works as above
Apple 0x021d (Aluminum MINI Keyboard ANSI) - Works as above

Other keyboards
Microsoft Internet Keyboard c. 1999 - no special functions on F-Keys. They come up as regular F-Keys
Random Logitec - same as above, normal F-keys

An interesting note:
The QMK built-in keycodes for Brightness (KC_BRID and KC_BRIU, consumer codes 0x006F and 0x0070) don't work on my external monitor, only the built-in display of the MBA.

@lordpixel23
Copy link

Having just read the entire thread can I ask if @drashna 's patch in on track to be integrated into the upstream main/master?

If I am following then it seems like it can be used along with SHARED_EP to activate some basic functionality such as Globe+E for Emojis and/or it can be used as a layer activation key along with a layer which "emulates" the other functionality a "real" fn key provides.

This seems worth landing?

@HVR88
Copy link

HVR88 commented Nov 1, 2023

@fauxpark's PR was merged to the develop branch here: qmk/qmk_firmware#22256

If I am following then it seems like it can be used along with SHARED_EP to activate some basic functionality such as Globe+E for Emojis

Yes.

and/or it can be used as a layer activation key along with a layer which "emulates" the other functionality a "real" fn key provides.

Not by simply using LT alone, because it doesn't include some of the changes @drashna made. Namely, the globe key isn't defined as a basic keycode. You should be able to do it by including a custom condition in process_record_user() to check for the two key press types (tap and hold) on a placeholder, and then manually send the globe key on the tap.

Example placeholder:

#define KC_MYGLOBELAYERKEY LT(0, KC_ESC)

@lordpixel23
Copy link

Right. That's my question. I believe @fauxpark made reference to not defining a KC_ code yet and @drashna did take that additional step in their patch. So I am trying to understand if the plan is to make a second merge with additional changes or if there is some reason for not adding this as a basic keycode?

@fauxpark
Copy link
Author

fauxpark commented Nov 2, 2023

I didn't add a keycode because this usage only really does something useful on macOS, and even then you still need an Apple VID/PID pair for it to work properly, as has been mentioned several times already, and in my PR. So it's not like you can use the Configurator due to this restriction anyway; for now you can make a custom keycode like so.

@lordpixel23
Copy link

Thanks for the summary. I did read the whole history back to 2017 so I have a decent handle on it I think. I get that if you want a true Apple fn key which works like a native keyboard the VID/PID issue is still there and I suppose that means support in the GUI tools is not appropriate as the restrictions will confuse people who haven't followed the whole context. I do see value in being able to do the Globe+E shortcuts etc.

Anyway, thanks for the link to that comment link. That makes things clearer.

@HVR88
Copy link

HVR88 commented Nov 3, 2023

I didn't add a keycode because this usage only really does something useful on macOS, and even then you still need an Apple VID/PID pair for it to work properly,

There are many other codes already in QMK's basic list that only work on one platform, as a comparison.

IMO, the VID/PID issue isn't really relevant to the Globe/Fn key - it's relevant to Mac OS's support of Function keys (F1 to F12). Globe/Fn shortcuts work without Apple VID/PID (Emoji without combo, Globe-E, Globe-H, Globe-N etc.) It's the function keys that don't work without Apple's VID/PID - F1/F2 for brightness, etc. And with or without Apple VID/PID, you can't get the same behavior as a real Apple keyboard where Globe/Fn+F1 = real F1 so that's moot.

This is why Apple's accessories implementation document spells out the consumer code for others to use as Globe, but doesn't mention anything about its use with the Function keys.

I'm actually using Apple VID/PID on my keyboard right now and for only one reason. To make sure that screen brightness control on F1 and F2 work with both internal and external display. Otherwise everything else can be done with codes built into QMK and layer shifts.

@lordpixel23
Copy link

I wasn't going to say anything further but I think @HVR88 makes the case well. There is value in adding the key code to to make Globe available as an extra key code separately from the issue of VID/PID and function keys. It enables one to access most of the functionality.

@chrismanderson
Copy link

I was catching up on this thread and wanted to sum up a bit as it's exciting to see some progress being made, but understandably a bit tougher to follow the current state of this issue.

  1. Recently, Apple added a consumer code for the Globe Key (0x029D) to the keyboard consumer page for keyboards on Apple devices. This means that there is now an actual key code for the Globe key that can be used in QMK and that macOS can recognize. This means that the original patch in this gist is no longer required.

  2. To use the new Globe key key code,

    1. Apply the code provided by @drashna (drashna/qmk_firmware@fb94bc4)
    2. Set KEYBOARD_SHARED_EP = yes in your rules.mk file for your specific keyboard.
    3. Add KC_GLOBE to your keymap file
  3. In doing so, some of the Globe/Fn keyboard shortcuts will work properly, but not all. Namely, the F1-12 keys do not work as expected. There are other additional not well understood quirks, such as the emoji picker displaying no matter how long you hold the custom Globe key (where with a 'real' Globe key, the picker does not display if you hold it)

  4. In order to support the special functions of the F-keys, you still need to use the custom VID/PID linked at the top of the gist.

  5. Some of the functionality provided by @drashna has been merged into QMK's develop branch (qmk/qmk_firmware#22256). Notably, this does not include the actual key code you can use in your keymap.

  6. You cannot use KC_GLOBE as a layer modifier key because layer-tap/mod-tap only support basic key codes.

@HVR88
Copy link

HVR88 commented Nov 7, 2023

Mostly.

  1. To use the new Globekey code,
    i. Apply the code provided by @fauxpark or pull the master branch clone provided by @drashna
    iii. Add KC_GLOBE to your keymap file only for @drashna's branch

  2. Fn shortcuts work - it would be good to find/list any that don't (F1-12 not included, see #4). Emoji picker shows up on key-release after a long hold of the Fn key, which doesn't match behavior of real Apple keyboard (VID/PID has no effect on this)

  3. F1-12 keys don't work at all without Apple VID/PID and this has nothing to do with the Fn/Globe functionality

  4. @fauxpark's code has been merged into QMK's development branch

  5. You can't use KC_GLOBE as a layer modifier with @fauxpark's merged code because there is no KC_GLOBE defined as a basic keycode. You can use KC_GLOBE as a layer modifier with @drashna's branch because that keycode is defined.

  6. The overall behavior using 0x029D seems to be working as Apple intended - macOS bugs notwithstanding.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment