qmk/docs/features/os_detection.md
Andre Brait 7f475b590a
Tweak OS detect, add OS_DETECTION_SINGLE_REPORT (#24379)
* Default OS_DETECTION_DEBOUNCE bumped from 200ms to 250ms
* Add OS_DETECTION_SINGLE_REPORT to prevent undesired multiple reports
* Prevents random stability issues on ARM MacBooks after switching via KVM
* Works for every device I could test, including ARM MacBooks
* Disabled by default to keep current behavior
* Add Troubleshooting section on documentation
* Tweak reset logic to prevent a freeze with some KVMs

The USB stack on ARM MacBooks is more similar to that of iOS and,
for some reason, it seems to like sending packets that influence
the OS detection and results in a second OS_MACOS report being sent
at a random period of time after plugging the keyboard back. This
does not always happen and the consequences of this vary based on
what the user is doing in the callback, but since this is not
obvious and it's hard to debug, I've decided to add a flag for
those affected by such issue. The stability issue I had in mine was
a combination of factors and I found the actual cause being my own
bad math when changing the default layer, but this change alone is
also confirmed to fix it. Lastly, soem KVMs seem to leave the USB
controlled in a suspended state when cold-booting Windows, meaning
the keyboard would hang and the reset logic would not work. This
tunes it so that it can get out of such state. Also retested for
compatibility with my old KVM to ensure the logic works for both.
2024-10-06 21:50:39 +13:00

5.0 KiB

OS Detection

This feature makes a best guess at the host OS based on OS specific behavior during USB setup. It may not always get the correct OS, and shouldn't be relied on as for critical functionality.

Using it you can have OS specific key mappings or combos which work differently on different devices.

It is available for keyboards which use ChibiOS, LUFA and V-USB.

Usage

In your rules.mk add:

OS_DETECTION_ENABLE = yes

It will automatically include the required headers file. It declares os_variant_t detected_host_os(void); which you can call to get detected OS.

It returns one of the following values:

enum {
    OS_UNSURE,
    OS_LINUX,
    OS_WINDOWS,
    OS_MACOS,
    OS_IOS,
} os_variant_t;

::: tip Note that it takes some time after firmware is booted to detect the OS. ::: This time is quite short, probably hundreds of milliseconds, but this data may be not ready in keyboard and layout setup functions which run very early during firmware startup.

Callbacks

If you want to perform custom actions when the OS is detected, then you can use the process_detected_host_os_kb function on the keyboard level source file, or process_detected_host_os_user function in the user keymap.c.

bool process_detected_host_os_kb(os_variant_t detected_os) {
    if (!process_detected_host_os_user(detected_os)) {
        return false;
    }
    switch (detected_os) {
        case OS_MACOS:
        case OS_IOS:
            rgb_matrix_set_color_all(RGB_WHITE);
            break;
        case OS_WINDOWS:
            rgb_matrix_set_color_all(RGB_BLUE);
            break;
        case OS_LINUX:
            rgb_matrix_set_color_all(RGB_ORANGE);
            break;
        case OS_UNSURE:
            rgb_matrix_set_color_all(RGB_RED);
            break;
    }
    
    return true;
}

OS detection stability

The OS detection is currently handled while the USB device descriptor is being assembled. The process is done in steps, generating a number of intermediate results until it stabilizes. We therefore resort to debouncing the result until it has been stable for a given amount of milliseconds. This amount can be configured, in case your board is not stable within the default debouncing time of 200ms.

Configuration Options

  • #define OS_DETECTION_DEBOUNCE 250
    • defined the debounce time for OS detection, in milliseconds
    • defaults to 250ms
  • #define OS_DETECTION_KEYBOARD_RESET
    • enables the keyboard reset upon a USB device reinitilization
    • this setting may help with detection issues when switching between devices on some KVMs (see Troubleshooting)
  • #define OS_DETECTION_SINGLE_REPORT
    • allows the report callbacks to be called only once, when the OS detection result is considered stable
    • subsequent changes in the detection results, if any, are ignored
    • this setting may help with delayed stability issues when switching devices on some KVMs (see Troubleshooting)

Troubleshooting

Some KVMs and USB switches may cause issues when the OS detection is turned on. Here is a list of common issues and how to fix them:

  • Problem: keyboard won't redetect the OS when switching between machines using a KVM
    • Explanation: some KVMs keep the USB controller powered on during the switch and OS detection happens when the USB device description is being assembled.
    • Solution: use OS_DETECTION_KEYBOARD_RESET to force the keyboard to reset upon switching.
  • Problem: keyboard OS detection callback gets invoked even minuted after startup
    • Explanation: some OSes, notably macOS on ARM-based Macs, may cause this behavior. The actual cause is not known at this time.'
    • Solution: use OS_DETECTION_SINGLE_REPORT to suppress repeated callback invocations.

Debug

If OS is guessed incorrectly, you may want to collect data about USB setup packets to refine the detection logic.

To do so in your config.h add:

#define OS_DETECTION_DEBUG_ENABLE

And in your rules.mk add:

CONSOLE_ENABLE = yes

And also include "os_detection.h" in your keymap.c.

Then you can define custom keycodes to store data about USB setup packets in EEPROM (persistent memory) and to print it later on host where you can run qmk console:

enum custom_keycodes {
    STORE_SETUPS = SAFE_RANGE,
    PRINT_SETUPS,
};

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
    switch (keycode) {
        case STORE_SETUPS:
            if (record->event.pressed) {
                store_setups_in_eeprom();
            }
            return false;
        case PRINT_SETUPS:
            if (record->event.pressed) {
                print_stored_setups();
            }
            return false;
    }
}

Then please open an issue on Github with this information and tell what OS was not detected correctly and if you have any intermediate devices between keyboard and your computer.

Credits

Original idea is coming from FingerprintUSBHost project.