Skip to content

Instantly share code, notes, and snippets.

@arkku
Last active March 3, 2024 14:22
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arkku/95e661db1377342a7ce570a8d5bc9850 to your computer and use it in GitHub Desktop.
Save arkku/95e661db1377342a7ce570a8d5bc9850 to your computer and use it in GitHub Desktop.
Patch for QMK firmware to add two keycodes for Apple Fn: KC_APFN simulates the key directly, whereas APFN_LAYER(x) works as MO(x) but also registers as holding down Apple Fn _when no other key is pressed_. APFN_KEY(x) is key x together with Apple Fn.
diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk
index c976b8296d..335b432e78 100644
--- a/builddefs/common_features.mk
+++ b/builddefs/common_features.mk
@@ -108,6 +108,12 @@ ifeq ($(strip $(MOUSEKEY_ENABLE)), yes)
SRC += $(QUANTUM_DIR)/mousekey.c
endif
+ifeq ($(strip $(APPLE_FN_ENABLE)), yes)
+ OPT_DEFS += -DAPPLE_FN_ENABLE -DENABLE_APPLE_FN_KEY=1
+else
+ OPT_DEFS += -DENABLE_APPLE_FN_KEY=0
+endif
+
VALID_POINTING_DEVICE_DRIVER_TYPES := adns5050 adns9800 analog_joystick cirque_pinnacle_i2c cirque_pinnacle_spi pmw3360 pmw3389 pimoroni_trackball custom
ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes)
ifeq ($(filter $(POINTING_DEVICE_DRIVER),$(VALID_POINTING_DEVICE_DRIVER_TYPES)),)
diff --git a/keyboards/gmmk/pro/config.h b/keyboards/gmmk/pro/config.h
index aba69c8872..aa1a262d90 100644
--- a/keyboards/gmmk/pro/config.h
+++ b/keyboards/gmmk/pro/config.h
@@ -20,8 +20,13 @@
/* USB Device descriptor parameter */
#define DEVICE_VER 0x0001
+#if ENABLE_APPLE_FN_KEY
+#define VENDOR_ID 0x05AC
+#define PRODUCT_ID 0x021E
+#else
#define VENDOR_ID 0x320F
#define PRODUCT_ID 0x5044
+#endif
#define MANUFACTURER Glorious
#define PRODUCT GMMK Pro
diff --git a/keyboards/gmmk/pro/rev1/iso/rules.mk b/keyboards/gmmk/pro/rev1/iso/rules.mk
index d3afa4d432..fccc7035b6 100644
--- a/keyboards/gmmk/pro/rev1/iso/rules.mk
+++ b/keyboards/gmmk/pro/rev1/iso/rules.mk
@@ -8,14 +8,15 @@ BOOTLOADER = stm32-dfu
# change yes to no to disable
#
BOOTMAGIC_ENABLE = yes # Enable Bootmagic Lite
-MOUSEKEY_ENABLE = yes # Mouse keys
+MOUSEKEY_ENABLE = no # Mouse keys
EXTRAKEY_ENABLE = yes # Audio control and System control
CONSOLE_ENABLE = no # Console for debug
COMMAND_ENABLE = no # Commands for debug and configuration
-NKRO_ENABLE = yes # Enable N-Key Rollover
+NKRO_ENABLE = no # Enable N-Key Rollover
BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality
RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow
AUDIO_ENABLE = no # Audio output
ENCODER_ENABLE = yes
RGB_MATRIX_ENABLE = yes
RGB_MATRIX_DRIVER = AW20216
+APPLE_FN_ENABLE = yes
diff --git a/quantum/action.c b/quantum/action.c
index 4e81a5466f..cc6e53c102 100644
--- a/quantum/action.c
+++ b/quantum/action.c
@@ -306,6 +306,10 @@ void register_button(bool pressed, enum mouse_buttons button) {
}
#endif
+#if ENABLE_APPLE_FN_KEY
+static bool virtual_apple_fn_on = false;
+#endif
+
/** \brief Take an action and processes it.
*
* FIXME: Needs documentation.
@@ -585,6 +589,53 @@ void process_action(keyrecord_t *record, action_t action) {
layer_off(action.layer_mods.layer);
}
break;
+#if ENABLE_APPLE_FN_KEY
+ case ACT_APPLE_FN:
+ if (event.pressed) {
+ if (action.key.code) {
+ if (!virtual_apple_fn_on) {
+ add_key(KC_APPLE_FN);
+ send_keyboard_report();
+ virtual_apple_fn_on = true;
+ }
+ add_key(action.key.code);
+ send_keyboard_report();
+ } else {
+ virtual_apple_fn_on = false;
+ register_code(KC_APPLE_FN);
+ }
+ } else {
+ if (action.key.code) {
+ del_key(action.key.code);
+ if (virtual_apple_fn_on) {
+ del_key(KC_APPLE_FN);
+ virtual_apple_fn_on = false;
+ }
+ send_keyboard_report();
+ } else {
+ unregister_code(KC_APPLE_FN);
+ }
+ }
+ break;
+#ifdef NO_ACTION_TAPPING
+ case ACT_LAYER_TAP:
+ case ACT_LAYER_TAP_EXT:
+ if (action.layer_tap.code == OP_APPLE_FN) {
+ if (event.pressed) {
+ layer_on(action.layer_tap.val);
+ register_code(KC_APPLE_FN);
+ virtual_apple_fn_on = true;
+ } else {
+ if (virtual_apple_fn_on) {
+ virtual_apple_fn_on = false;
+ unregister_code(KC_APPLE_FN);
+ }
+ layer_off(action.layer_tap.val);
+ }
+ }
+ break;
+#endif
+#endif
# ifndef NO_ACTION_TAPPING
case ACT_LAYER_TAP:
case ACT_LAYER_TAP_EXT:
@@ -653,6 +704,21 @@ void process_action(keyrecord_t *record, action_t action) {
}
break;
# endif
+#if ENABLE_APPLE_FN_KEY
+ case OP_APPLE_FN:
+ if (event.pressed) {
+ layer_on(action.layer_tap.val);
+ register_code(KC_APPLE_FN);
+ virtual_apple_fn_on = true;
+ } else {
+ if (virtual_apple_fn_on) {
+ virtual_apple_fn_on = false;
+ unregister_code(KC_APPLE_FN);
+ }
+ layer_off(action.layer_tap.val);
+ }
+ break;
+#endif
default:
/* tap key */
if (event.pressed) {
@@ -892,6 +958,13 @@ __attribute__((weak)) void register_code(uint8_t code) {
*/
#endif
{
+#if ENABLE_APPLE_FN_KEY
+ if (virtual_apple_fn_on) {
+ del_key(KC_APPLE_FN);
+ send_keyboard_report();
+ virtual_apple_fn_on = false;
+ }
+#endif
// Force a new key press if the key is already pressed
// without this, keys with the same keycode, but different
// modifiers will be reported incorrectly, see issue #1708
@@ -919,6 +992,12 @@ __attribute__((weak)) void register_code(uint8_t code) {
mousekey_send();
}
#endif
+#if ENABLE_APPLE_FN_KEY
+ else if (code == KC_APPLE_FN) {
+ add_key(code);
+ send_keyboard_report();
+ }
+#endif
}
/** \brief Utilities for actions. (FIXME: Needs better description)
@@ -979,6 +1058,12 @@ __attribute__((weak)) void unregister_code(uint8_t code) {
mousekey_send();
}
#endif
+#if ENABLE_APPLE_FN_KEY
+ else if (code == KC_APPLE_FN) {
+ del_key(code);
+ send_keyboard_report();
+ }
+#endif
}
/** \brief Tap a keycode with a delay.
diff --git a/quantum/action_code.h b/quantum/action_code.h
index 20b3e459d2..4409ac4618 100644
--- a/quantum/action_code.h
+++ b/quantum/action_code.h
@@ -51,6 +51,7 @@ 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|0001 xxxx xxxx Apple Fn + key
* 0111|xxxx xxxx xxxx (reserved)
*
* Layer Actions(10xx)
@@ -77,8 +78,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
* 101E|LLLL|1111 0010 Off/On (0xF2) [NOT TAP]
* 101E|LLLL|1111 0011 Set/Clear (0xF3) [NOT TAP]
* 101E|LLLL|1111 0100 One Shot Layer (0xF4) [TAP]
- * 101E|LLLL|1111 xxxx Reserved (0xF5-FF)
- * ELLLL: layer 0-31(E: extra bit for layer 16-31)
+ * 101E|LLLL|1111 0101 Apple Fn Layer (0xF5)
+ * 101E|LLLL|1111 xxxx Reserved (0xF6-FF)
*/
enum action_kind_id {
/* Key Actions */
@@ -98,6 +99,9 @@ enum action_kind_id {
ACT_LAYER_MODS = 0b1001,
ACT_LAYER_TAP = 0b1010, /* Layer 0-15 */
ACT_LAYER_TAP_EXT = 0b1011, /* Layer 16-31 */
+#if ENABLE_APPLE_FN_KEY
+ ACT_APPLE_FN = 0b0111
+#endif
};
/** \brief Action Code Struct
@@ -222,6 +226,9 @@ enum layer_param_tap_op {
OP_OFF_ON,
OP_SET_CLEAR,
OP_ONESHOT,
+#if ENABLE_APPLE_FN_KEY
+ OP_APPLE_FN
+#endif
};
#define ACTION_LAYER_BITOP(op, part, bits, on) ACTION(ACT_LAYER, (op) << 10 | (on) << 8 | (part) << 5 | ((bits)&0x1f))
#define ACTION_LAYER_TAP(layer, key) ACTION(ACT_LAYER_TAP, (layer) << 8 | (key))
@@ -254,6 +261,12 @@ enum layer_param_tap_op {
#define ACTION_DEFAULT_LAYER_BIT_XOR(part, bits) ACTION_LAYER_BITOP(OP_BIT_XOR, (part), (bits), 0)
#define ACTION_DEFAULT_LAYER_BIT_SET(part, bits) ACTION_LAYER_BITOP(OP_BIT_SET, (part), (bits), 0)
+#if ENABLE_APPLE_FN_KEY
+#define ACTION_APPLE_FN_KEY(key) ACTION(ACT_APPLE_FN, (key))
+#define ACTION_APPLE_FN() ACTION_APPLE_FN_KEY(0)
+#define ACTION_APPLE_FN_LAYER(layer) ACTION_LAYER_TAP((layer), OP_APPLE_FN)
+#endif
+
/* OneHand Support */
enum swap_hands_param_tap_op {
OP_SH_TOGGLE = 0xF0,
diff --git a/quantum/keycode.h b/quantum/keycode.h
index 3c80a386d1..762b1bf73f 100644
--- a/quantum/keycode.h
+++ b/quantum/keycode.h
@@ -502,9 +502,17 @@ enum internal_special_keycodes {
KC_MEDIA_FAST_FORWARD,
KC_MEDIA_REWIND,
KC_BRIGHTNESS_UP,
- KC_BRIGHTNESS_DOWN
+ KC_BRIGHTNESS_DOWN,
+
+#if ENABLE_APPLE_FN_KEY
+ KC_APFN,
+#endif
};
+#if ENABLE_APPLE_FN_KEY
+#define KC_APPLE_FN KC_APFN
+#endif
+
enum mouse_keys {
/* Mouse Buttons */
#ifdef VIA_ENABLE
diff --git a/quantum/keymap_common.c b/quantum/keymap_common.c
index c1940f0fd3..efda95d52c 100644
--- a/quantum/keymap_common.c
+++ b/quantum/keymap_common.c
@@ -58,6 +58,17 @@ action_t action_for_keycode(uint16_t keycode) {
case KC_LEFT_CTRL ... KC_RIGHT_GUI:
action.code = ACTION_KEY(keycode);
break;
+#if ENABLE_APPLE_FN_KEY
+ case KC_APPLE_FN:
+ action.code = ACTION_APPLE_FN();
+ break;
+ case QK_APPLE_FN_MOD ... QK_APPLE_FN_MOD_MAX:
+ action.code = ACTION_APPLE_FN_KEY(keycode & 0xFF);
+ break;
+ case QK_APPLE_FN ... QK_APPLE_FN_MAX:
+ action.code = ACTION_APPLE_FN_LAYER(keycode & 0x1F);
+ break;
+#endif
#ifdef EXTRAKEY_ENABLE
case KC_SYSTEM_POWER ... KC_SYSTEM_WAKE:
action.code = ACTION_USAGE_SYSTEM(KEYCODE2SYSTEM(keycode));
diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h
index 40355d799a..c99a814cf6 100644
--- a/quantum/quantum_keycodes.h
+++ b/quantum/quantum_keycodes.h
@@ -37,6 +37,12 @@ enum quantum_keycodes {
QK_RALT = 0x1400,
QK_RGUI = 0x1800,
QK_MODS_MAX = 0x1FFF,
+#if ENABLE_APPLE_FN_KEY
+ QK_APPLE_FN_MOD = 0x3E00,
+ QK_APPLE_FN_MOD_MAX = 0x3EFF,
+ QK_APPLE_FN = 0x3FE0,
+ QK_APPLE_FN_MAX = 0x3FFF,
+#endif
QK_LAYER_TAP = 0x4000,
QK_LAYER_TAP_MAX = 0x4FFF,
QK_TO = 0x5000,
@@ -823,6 +829,11 @@ enum quantum_keycodes {
// L-ayer, T-ap - 256 keycode max, 16 layer max
#define LT(layer, kc) (QK_LAYER_TAP | (((layer)&0xF) << 8) | ((kc)&0xFF))
+#if ENABLE_APPLE_FN_KEY
+#define APFN_LAYER(layer) (QK_APPLE_FN | ((layer) & 0x1F))
+#define APFN_KEY(key) (QK_APPLE_FN_MOD | ((key) & 0xFF))
+#endif
+
// M-od, T-ap - 256 keycode max
#define MT(mod, kc) (QK_MOD_TAP | (((mod)&0x1F) << 8) | ((kc)&0xFF))
diff --git a/tmk_core/protocol/report.c b/tmk_core/protocol/report.c
index 5755098c60..ab596b603e 100644
--- a/tmk_core/protocol/report.c
+++ b/tmk_core/protocol/report.c
@@ -241,6 +241,12 @@ void del_key_bit(report_keyboard_t* keyboard_report, uint8_t code) {
* FIXME: Needs doc
*/
void add_key_to_report(report_keyboard_t* keyboard_report, uint8_t key) {
+#if ENABLE_APPLE_FN_KEY
+ if (key == KC_APPLE_FN) {
+ keyboard_report->reserved = 1;
+ return;
+ }
+#endif
#ifdef NKRO_ENABLE
if (keyboard_protocol && keymap_config.nkro) {
add_key_bit(keyboard_report, key);
@@ -255,6 +261,12 @@ void add_key_to_report(report_keyboard_t* keyboard_report, uint8_t key) {
* FIXME: Needs doc
*/
void del_key_from_report(report_keyboard_t* keyboard_report, uint8_t key) {
+#if ENABLE_APPLE_FN_KEY
+ if (key == KC_APFN) {
+ keyboard_report->reserved = 0;
+ return;
+ }
+#endif
#ifdef NKRO_ENABLE
if (keyboard_protocol && keymap_config.nkro) {
del_key_bit(keyboard_report, key);
diff --git a/tmk_core/protocol/usb_descriptor.c b/tmk_core/protocol/usb_descriptor.c
index 063bd2c3f1..a7fc5672b6 100644
--- a/tmk_core/protocol/usb_descriptor.c
+++ b/tmk_core/protocol/usb_descriptor.c
@@ -71,10 +71,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),
+
+#if ENABLE_APPLE_FN_KEY
+ 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),
@arkku
Copy link
Author

arkku commented Jul 27, 2022

Patch for QMK firmware to add three keycodes for Apple Fn: KC_APFN simulates the key directly, whereas APFN_LAYER(x) works as MO(x), but also registers as holding down Apple Fn when no other key is pressed. Pressing another key releases Apple Fn. This allows implementing a virtual Apple Fn with a layer, like you would with MO(x), but also enables holding down Fn to select text in Terminal.app. Another important benefit is that the layer will work on non-Apple systems that ignore the Apple Fn key. Meanwhile, APFN_KEY(x) works as a combination of the key x and Apple Fn. For example, APFN_KEY(KC_F1) can be used to trigger Fn + F1.

To use these you need to use Apple's USB vendor id and set APPLE_FN_ENABLE = yes. Not compatible with NKRO. Parts of the patch are from this gist.

To use these from QMK Configurator, use the Quantum -> Any key, and type KC_APFN for Apple Fn, or, e.g., APFN_LAYER(3) to momentarily toggle to layer 3 while also registering Apple Fn as being held down. Remember to update your keyboard's config.h to set the USB vendor id and rules.mk to set APPLE_FN_ENABLE = yes. Patch updates the GMMK Pro rev1 keyboard as an example.

Apply with git apply.

@arkku
Copy link
Author

arkku commented Aug 6, 2022

Update: I added APFN_KEY(x) to combine a keypress with Apple Fn. The use case is with APFN_LAYER, which releases Fn when another key is pressed. For keys where you wish to keep Fn even with that layer, you can use something like APFN_KEY(KC_F1) to force Apple Fn down together with that key.

@nitsujri
Copy link

nitsujri commented Dec 9, 2022

@arkku this is super cool. I've just started playing around w/ having the Apple Key on my redox. There were some pretty big breaking changes recently, think it's possible to modify this patch to match the updated code?

I'm really interested in the APFN_KEY() because really that's what we want. We want a "True F11" and it doesn't matter what the layer/other-key-combos are doing.

Thanks!

@arkku
Copy link
Author

arkku commented Dec 12, 2022

@nitsujri Hi, I can look into it at some point, but you may have luck with this fork of QMK, branch feature/apple_fn: https://github.com/arkku/qmk_firmware/tree/feature/apple_fn

@nitsujri
Copy link

I just tried to merge qmk master into your apple_fn branch, there's only 3 files that conflict but they conflict in ways that I don't understand (something like 5k files changed,phew!). Like I can't find where:

#define KC_INT6 KC_INTERNATIONAL_6
#define KC_INT7 KC_INTERNATIONAL_7
#define KC_INT8 KC_INTERNATIONAL_8
#define KC_INT9 KC_INTERNATIONAL_9
#define KC_LNG1 KC_LANGUAGE_1
#define KC_LNG2 KC_LANGUAGE_2
#define KC_LNG3 KC_LANGUAGE_3
#define KC_LNG4 KC_LANGUAGE_4
#define KC_LNG5 KC_LANGUAGE_5
#define KC_LNG6 KC_LANGUAGE_6
#define KC_LNG7 KC_LANGUAGE_7
#define KC_LNG8 KC_LANGUAGE_8

This block of keycode.h is now gone? as well as a large chunk of quantum_keycodes.h has disappeared.

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