Skip to content

Instantly share code, notes, and snippets.

@fauxpark
Last active November 29, 2024 08:21
Show Gist options
  • Save fauxpark/010dcf5d6377c3a71ac98ce37414c6c4 to your computer and use it in GitHub Desktop.
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)
@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

@real-felix
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.

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