Skip to content

Instantly share code, notes, and snippets.

@fauxpark
Last active October 11, 2024 12:41
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)
@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.

@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