Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] [PR Planning] macOS Mission Control and Launchpad keys #10111

Closed
1 of 4 tasks
ghost opened this issue Aug 20, 2020 · 29 comments
Closed
1 of 4 tasks

[Feature Request] [PR Planning] macOS Mission Control and Launchpad keys #10111

ghost opened this issue Aug 20, 2020 · 29 comments

Comments

@ghost
Copy link

ghost commented Aug 20, 2020

Feature Request Type

  • Core functionality
  • Add-on hardware support (eg. audio, RGB, OLED screen, etc.)
  • Alteration (enhancement/optimization) of existing feature(s)
  • New behavior

Description

Hello, I'm getting ready to submit a Pull Request to add Mission Control and Launchpad keycodes (link below).

https://github.com/controlxcv/qmk_firmware/tree/pr_mac_desktop_keys

I'm not sure whether to target master or develop for this one. The PR Checklist says to target develop for changes to Core, but in this case I'm not sure if this falls into "real" Core territory even if the changes are to files in quantum and tmk_core. My current impression is that Core functionality refers to more of low level microcontroller code and less of adding new keycodes.

I've already tested my changes by running make all:default. I've also flashed the keycodes on my development macropad and tried them out on my computers without problems.

I'd appreciate any comments or advice. Thanks!

@fauxpark
Copy link
Member

There was already a similar PR here: #4762
However we are waiting to be able to remove the KC_FN* keycodes before we can add new media keys, as there is only one spot left, and I don't like the idea of splitting it up like this.

@ghost
Copy link
Author

ghost commented Aug 21, 2020

Hi Ryan, I agree -- if those KC_FN* keycodes can be safely removed, we would have a nice and wide range for more new media keys. What I did to work around that was to slot the two new keycodes into 0xE8 and 0xE9, after the slots for the modifier keys. This would reduce the number of available slots to 0xEA to 0xEF, and the entire space would be full after that.

Is this what you meant by "one spot left"? Were you referring to the 0xE8 to 0xEF range that comes after "Keyboard Right GUI"?

@fauxpark
Copy link
Member

From one of my comments on that PR:

There is now only one free spot left after the screen brightness keycodes (0xBF). The rest of the space has been already filled.

Basically, it would be best if there were a single contiguous range for these keycodes, instead of placing them wherever just because there is free space.

@ghost
Copy link
Author

ghost commented Aug 21, 2020

Hi Ryan, I got it. Having the media keys fragmented all over the space isn't good for maintainability in the long term. I don't think I'll push through with this PR using the current keycode assignments. I think this issue can be closed unless anyone would like to chime in with additional comments.

For people who wish to have "AC Desktop Show All Windows" and "AC Desktop Show All Applications" keys on their keyboards right now, there is a way to do this using user-defined keycodes and calling host_consumer_send. I imagine that it might take a while to fully retire KC_FN*, so I'll put up some sample code a little later for everyone's reference.

@fauxpark
Copy link
Member

Feel free to leave this issue open, as I'd also like to see these keycodes added. It's really just the removal of those deprecated FN keycodes blocking this, and I've already done a ton of work to migrate most of the instances over in user keymaps. I think there's just a handful left to do.

@ghost
Copy link
Author

ghost commented Aug 21, 2020

Here's some code I came up with that lets me use those two keycodes in the meantime:

https://github.com/controlxcv/qmk_firmware/commit/d1aab46cf76edd3ab630678582722f1af758662f

The summary of it is that I use the Custom Keycodes feature, and call host_consumer_send(usage_id) from inside process_record_user.

Of course, there might be better approaches. I'm very much open to comments and suggestions.

@stale
Copy link

stale bot commented Nov 20, 2020

This issue has been automatically marked as stale because it has not had activity in the last 90 days. It will be closed in the next 30 days unless it is tagged properly or other activity occurs.
For maintainers: Please label with bug, in progress, on hold, discussion or to do to prevent the issue from being re-flagged.

@stale stale bot added the stale Issues or pull requests that have become inactive without resolution. label Nov 20, 2020
@stale stale bot removed the stale Issues or pull requests that have become inactive without resolution. label Nov 20, 2020
@geocine
Copy link

geocine commented Dec 29, 2020

@controlxcv , is there a full list of hex keycodes that Mac uses? May you share it. I am curious where you found these?

_AC_SHOW_ALL_WINDOWS = 0x29F,
_AC_SHOW_ALL_APPS    = 0x2A0

@fauxpark
Copy link
Member

They're taken from the HID usage tables, in the Consumer page (page 117). There are a lot of usages defined here, but most of them are not actually implemented in Windows/macOS/Linux.

@geocine
Copy link

geocine commented Dec 30, 2020

Thanks @fauxpark I'm blind I just saw the comment which links to that PDF

@geocine
Copy link

geocine commented Dec 30, 2020

Here's some code I came up with that lets me use those two keycodes in the meantime:

controlxcv@d1aab46

The summary of it is that I use the Custom Keycodes feature, and call host_consumer_send(usage_id) from inside process_record_user.

Of course, there might be better approaches. I'm very much open to comments and suggestions.

@controlxcv I tried your code but its not working on my Mac 10.14.6 . Is there any additional configuration? I have logged it by doing this and it is showing successfully on QMK Toolbox.

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
    if(_KC_MCON == keycode || _KC_LPAD == keycode){
        uprintf("KL: kc: %u, col: %u, row: %u, pressed: %u\n", keycode, record->event.key.col, record->event.key.row, record->event.pressed);
    }
    // switch case for handling mac keycodes goes here
}

@ghost
Copy link
Author

ghost commented Dec 30, 2020

I tried your code but its not working on my Mac 10.14.6 . Is there any additional configuration? I have logged it by doing this and it is showing successfully on QMK Toolbox.

@geocine Can you share the full text of your keymap.c, and maybe an excerpt of your log?

@geocine
Copy link

geocine commented Dec 30, 2020

@controlxcv This is my entire keymap, excuse for the wall of text

#include QMK_KEYBOARD_H


/*----------------------------------------------------------
 * Key codes
 * https://www.usb.org/sites/default/files/hut1_2.pdf
 */

#define _KC_MCON _KC_SHOW_ALL_WINDOWS
#define _KC_LPAD _KC_SHOW_ALL_APPS

enum my_keycodes {
	_KC_SHOW_ALL_WINDOWS = SAFE_RANGE,
	_KC_SHOW_ALL_APPS
};

enum my_consumer_usages {
    _AC_SHOW_ALL_WINDOWS = 0x29F,
    _AC_SHOW_ALL_APPS    = 0x2A0
};

enum my_layers {
    _BASE = 0,
    _RISE = 1
};



const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    [_BASE] = LAYOUT(
		BL_BRTG, _KC_MCON, LT(1,KC_MUTE),
		RGB_M_T, KC_1, KC_KP_2,
                RGB_TOG, KC_LCTRL, KC_9),

    [_RISE] = LAYOUT(
		_______, _______, _______,
		_______, _______, _______,
                _______, _______, _______)
};


bool process_record_user(uint16_t keycode, keyrecord_t *record) {
  // If console is enabled, it will print the matrix position and status of each key pressed
#ifdef CONSOLE_ENABLE
    if(_KC_MCON == keycode || _KC_LPAD == keycode){
        uprintf("KL: kc: %u, col: %u, row: %u, pressed: %u\n", keycode, record->event.key.col, record->event.key.row, record->event.pressed);
    }
#endif
    switch (keycode) {
        case _KC_SHOW_ALL_WINDOWS:
            if (record->event.pressed) {
                host_consumer_send(_AC_SHOW_ALL_WINDOWS);
                uprint("_AC_SHOW_ALL_WINDOWS\n");
            } else {
                host_consumer_send(0);
            }
            return false; /* Skip all further processing of this key */

        case _KC_SHOW_ALL_APPS:
            if (record->event.pressed) {
                host_consumer_send(_AC_SHOW_ALL_APPS);
                uprint("_AC_SHOW_ALL_APPS\n");
            } else {
                host_consumer_send(0);
            }
            return false; /* Skip all further processing of this key */

        default:
            return true; /* Process all other keycodes normally */

    }
}

void keyboard_post_init_user(void) {

#ifdef RGBLIGHT_ENABLE
    // Call the post init code.
    rgblight_disable();
    rgblight_enable(); // enables Rgb, without saving settings
    rgblight_sethsv(180, 255, 255); // sets the color to teal/cyan without saving
#endif
    breathing_toggle();
    backlight_level(2);
}

#ifdef ENCODER_ENABLE
void encoder_update_user(uint8_t index, bool clockwise) {
    if (index == 0) {
        switch(layer_state){
            case _RISE:
                if (clockwise){
                    tap_code(KC_RIGHT);
                } else{
                    tap_code(KC_LEFT);
                }
                break;
            default:
                if (clockwise) {
                    tap_code(KC_VOLU);
                } else {
                    tap_code(KC_VOLD);
                }
                break;
        }
    }
}
#endif

#ifdef OLED_DRIVER_ENABLE
void oled_task_user(void) {
    // Host Keyboard Layer Status
    oled_write_P(PSTR("Hello STM32F103CBT6!"), false);
    // Host Keyboard LED Status
    led_t led_state = host_keyboard_led_state();
    oled_write_P(led_state.caps_lock ? PSTR("CAP ") : PSTR("    "), false);
}
#endif

Below is my logs

  > KL: kc: 23849, col: 1, row: 0, pressed: 1
    _AC_SHOW_ALL_WINDOWS
  > KL: kc: 23849, col: 1, row: 0, pressed: 0
  > KL: kc: 23849, col: 1, row: 0, pressed: 1
    _AC_SHOW_ALL_WINDOWS
  > KL: kc: 23849, col: 1, row: 0, pressed: 0
  > KL: kc: 23849, col: 1, row: 0, pressed: 1
    _AC_SHOW_ALL_WINDOWS
  > KL: kc: 23849, col: 1, row: 0, pressed: 0
  > KL: kc: 23849, col: 1, row: 0, pressed: 1
    _AC_SHOW_ALL_WINDOWS
  > KL: kc: 23849, col: 1, row: 0, pressed: 0
  > KL: kc: 23849, col: 1, row: 0, pressed: 1
    _AC_SHOW_ALL_WINDOWS
  > KL: kc: 23849, col: 1, row: 0, pressed: 0
  > KL: kc: 23849, col: 1, row: 0, pressed: 1
    _AC_SHOW_ALL_WINDOWS
  > KL: kc: 23849, col: 1, row: 0, pressed: 0

@ghost
Copy link
Author

ghost commented Dec 30, 2020

@geocine Gut feel tells me that the problem has something to do with the order of the defines and enums. I suspect that _AC_SHOW_ALL_WINDOWS isn't being substituted properly in process_record_user().

Can you try plugging in the actual hex code for _AC_SHOW_ALL_WINDOWS into host_consumer_send()? Basically this:

host_consumer_send(0x29F);

You can also try logging the actual value of _AC_SHOW_ALL_WINDOWS in your uprintf() statements.

Let me know how it goes.

@geocine
Copy link

geocine commented Dec 30, 2020

@controlxcv I'm running out of clues , I added some logs to show the actual value here , which is correct 671 for 0x29F

  > KL: kc: 23849, col: 1, row: 0, pressed: 1
    _AC_SHOW_ALL_WINDOWS 671
  > KL: kc: 23849, col: 1, row: 0, pressed: 0
  > KL: kc: 23849, col: 1, row: 0, pressed: 1
    _AC_SHOW_ALL_WINDOWS 671
  > KL: kc: 23849, col: 1, row: 0, pressed: 0
  > KL: kc: 23849, col: 1, row: 0, pressed: 1
    _AC_SHOW_ALL_WINDOWS 671
  > KL: kc: 23849, col: 1, row: 0, pressed: 0
  > KL: kc: 23849, col: 1, row: 0, pressed: 1
    _AC_SHOW_ALL_WINDOWS 671
  > KL: kc: 23849, col: 1, row: 0, pressed: 0
  > KL: kc: 23849, col: 1, row: 0, pressed: 1
    _AC_SHOW_ALL_WINDOWS 671
  > KL: kc: 23849, col: 1, row: 0, pressed: 0

I also passed the actual value directly, but that didn't work either

host_consumer_send(0x29F);

I am on tag 0.11.22 of QMK , I don't know if that helps.

@ghost
Copy link
Author

ghost commented Dec 30, 2020

@geocine 0x29F is the hexadecimal representation of decimal 671 -- they're the same.

It's been a while since I ran this code, to be honest. Let me bring out my macropad and compile against the current codebase to see if anything has changed.

@ghost
Copy link
Author

ghost commented Dec 30, 2020

@geocine I compiled your code against the latest master and it worked on my 3x3 macropad. Pressing the (0,1) key on my pad successfully launches Mission Control.

I'm running macOS Catalina 10.15.7 (19H114) on a 2017 MacBook Air (Intel).

My macropad is ymdk/ymd09.

I did comment out the keyboard_post_init_user, encoder_update_user, and oled_task_user because they're hardware-specific, but you might want to try commenting them out too just to narrow things down.

If you have a tag here in GitHub that you can share so I can see your entire codebase, it might be able to help. Meanwhile let me dig around a bit more.

@geocine
Copy link

geocine commented Dec 30, 2020

@controlxcv Here is the source https://github.com/geocine/f103x2 I am using an STM32F103 controller, I don't think that should be an issue as the keycodes are being sent based on the logs.

@ghost
Copy link
Author

ghost commented Dec 30, 2020

@geocine This is an original hardware design, I presume? Let me take a bit of time to read your files.

@geocine
Copy link

geocine commented Dec 30, 2020

@controlxcv Yes it is , its a 3x3 matrix, but the (0,2) is an encoder

@ghost
Copy link
Author

ghost commented Dec 30, 2020

@geocine Can you check if EXTRAKEY_ENABLE is really defined where it's supposed to be defined? I mean, not just by checking if it's in rules.mk but by doing #ifdefs and uprints in key parts of your code. EXTRAKEY_ENABLE is being ifdef'ed practically every function call, so you need to make sure that your 0x29F code makes it through from the process_record_user() function, all the way to that part where your board fires it through the cable to your computer.

For common boards like Preonic and XD75, it's enough to check rules.mk, but since you're making a full custom, it might be worthwhile to do deeper checks. I'm not sure, but this might be a good place to put in a debug uprint:

Unfortunately, full custom boards are beyond my understanding of QMK. What I can say is that you need to make sure that Usage IDs from the Consumer page in the USB HID spec are being properly sent by your board to your computer. As to how exactly, I think I'll have to defer this to someone with better understanding of lower level stuff.

@DeepSeaDelight
Copy link

So... based upon what I'm reading here, it seems like this issue has not yet been fixed and is still floating out there hoping for some soul to come by and resolve.

I'm trying to get a Monstargear XO87 RGB working with MacOS-specific media keys, like launching Mission Control and Launchpad, and it looks like I'm hitting a brick wall here since I don't exactly see any sort of solid instructions to make this happen anywhere on the web other than downloading yet another app to like BetterTouchTool to try to work around the issue.

What I'm also reading here is that @controlxcv 's program didn't quite resolve the issue, either....

This is really frustrating to see, especially as custom keebs are starting to become much more popular with working from home now a global phenomenon. There's probably other folks out there in my position that kinda gave up, too.

It's a strange irony that QMK and its assorted tools function perfectly on the MacOS Terminal, but aren't able to flash a keyboard to make it work with all Mac-specific keycodes.

If Keychron was able to get their MacOS media keys working on their boards, there must be some way QMK can get it working, too.

@ghost
Copy link
Author

ghost commented May 29, 2021

@DeepSeaDelight My conversation with geocine was a bit of a sidetrack; he was designing a full custom board that he couldn't get to work, and it was only minimally related to this issue.

You will want to look at the first comment of @fauxpark -- I was able to create working code, but we need to wait for some refactorings in the framework code before we can introduce more keycodes. Simply put, there are currently no more free slots and the refactor will solve that.

If you're just programming firmware for a personal keyboard, you can implement Mac keys in your keymap.c instead of putting them in framework-level code. Let me share the proof-of-concept code for my macropad that has proper Launchpad and Mission Control support:

https://github.com/controlxcv/qmk_firmware/tree/dev/keyboards/ymdk/ymd09/keymaps/controlxcv

Let me know if you have any questions.

Regards,
Jonathan

@ghost
Copy link
Author

ghost commented May 29, 2021

@DeepSeaDelight I edited the link just now. Please refresh. Sorry about that!

@DeepSeaDelight
Copy link

DeepSeaDelight commented May 29, 2021

@controlxcv

Wow, quick response! Thank you for letting me know what happened!

So, I'm seeing what you did in the keymap.c file, but now I'm trying to figure out how to integrate that bit of code into the .HEX file that generates from the online QMK Configurator. I then take that .HEX file and shove it into the QMK Toolbox and flash from there.

Any ideas? What portion of your proof-of-concept do I change in order to trigger Mission Control/Expose and Launchpad on F3 and F4 of an ANSI TKL layout, respectively?

@ghost
Copy link
Author

ghost commented May 29, 2021

@DeepSeaDelight First, some non-ideal news: QMK Configurator can't compile custom C code.

https://docs.qmk.fm/#/configurator_default_keymaps?id=custom-code

Features that require adding functions to the keymap.c file, such as Tap Dance or Unicode, can not be compiled in Configurator at all.

In addition to this, I don't think what you said about integrating code into the HEX file is possible. HEX files are compiled machine code and are practically impossible to modify by hand. You'll really need to take the C code route at the moment if you want to have those two keycodes on your keyboard. It will be a different story of course if those two codes have already been integrated into the framework.

Perhaps now is a good time to challenge yourself with writing your firmware in C. :) The extra code to enable those two keys isn't really that much.

That said, the underlying principle is straightforward. Mission Control and Launchpad are defined in the USB HID standard's "Consumer" page as shown below. (source: https://www.usb.org/sites/default/files/hut1_2.pdf)

Key             | Usage ID | Usage Name
-------------------------------------------------------------
Mission Control | 0x29F    | AC Desktop Show All Windows
Launchpad       | 0x2A0    | AC Desktop Show All Applications

QMK on the other hand supports the creation of new keycodes that can do practically anything you want through the process_record_user function, documented here: https://docs.qmk.fm/#/custom_quantum_functions?id=programming-the-behavior-of-any-keycode

QMK also has a function called host_consumer_send that transmits IDs from the "Consumer" page I mentioned above.

Put those three concepts together and you'd come up with code like this:

enum my_keycodes {
    KC_MISSION_CONTROL = SAFE_RANGE,
    KC_LAUNCHPAD
};

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
    switch (keycode) {

        case KC_MISSION_CONTROL:
            if (record->event.pressed) {
                host_consumer_send(0x29F);
            } else {
                host_consumer_send(0);
            }
            return false; /* Skip all further processing of this key */

        case KC_LAUNCHPAD:
            if (record->event.pressed) {
                host_consumer_send(0x2A0);
            } else {
                host_consumer_send(0);
            }
            return false; /* Skip all further processing of this key */

        default:
            return true; /* Process all other keycodes normally */

    }
}

With that code in your keymap.c, you'd be able to use KC_MISSION_CONTROL and KC_LAUNCHPAD in your layout definition as if they were any other ordinary QMK keycode.

Do let me know if you have any questions. Compile the default firmware for your board as a starting point, and then expand from there. Good luck!

@daaaaaaaaaniel
Copy link

daaaaaaaaaniel commented Mar 16, 2022

I'm seeing some odd behavior in my implementation of this. In my macOS System Preferences, I have mapped 'Show Launchpad' to F4. I have KC_F4 in my keymap on the _RAISE layer, and I can hold down the RAISE key and tap F4 repeatedly to toggle Launchpad open and closed. However, when I use the key code KC_LAUNCHPAD (also located on my _RAISE layer), Launchpad behaves differently from how I would have expected. Unlike the F4 key, which I can repeatedly tap without lifting the RAISE key, the KC_LAUNCHPAD key can only be toggled on and off if I release the RAISE key between key pressed. If I don't release the RAISE key, KC_LAUNCHPAD seems not to register. Any idea why this is?

Notably this isn't the case with KC_MISSION_CONTROL, which registers regardless of whether or not I lift the layer key in between taps of the Mission Control key.

@madwort
Copy link

madwort commented Jan 17, 2023

I was just trying to do this & came across this thread, for the benefit of future travellers I'm probably gonna stick with the default key sequences & use e.g. LCTL(KC_DOWN) in the online QMK configurator.

@drashna
Copy link
Member

drashna commented Mar 30, 2023

closing: #19884

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

No branches or pull requests

6 participants