[Core] Add OS detection callbacks (#21777)

This commit is contained in:
Andre Brait 2024-02-16 15:19:02 +01:00 committed by GitHub
parent 77e8867498
commit 80f3da36e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 440 additions and 61 deletions

View File

@ -14,7 +14,7 @@ In your `rules.mk` add:
OS_DETECTION_ENABLE = yes
```
Include `"os_detection.h"` in your `keymap.c`.
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:
@ -32,6 +32,54 @@ enum {
?> 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 :id=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`.
```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.
## KVM and USB switches
Some KVM and USB switches may not trigger the USB controller on the keyboard to fully reset upon switching machines.
If your keyboard does not redetect the OS in this situation, you can force the keyboard to reset when the USB initialization event is detected, forcing the USB controller to be reconfigured.
## Configuration Options
* `#define OS_DETECTION_DEBOUNCE 200`
* defined the debounce time for OS detection, in milliseconds
* `#define OS_DETECTION_KEYBOARD_RESET`
* enables the keyboard reset upon a USB device reinitilization, such as switching devices on some KVMs
## Debug
If OS is guessed incorrectly, you may want to collect data about USB setup packets to refine the detection logic.

View File

@ -137,6 +137,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifdef WPM_ENABLE
# include "wpm.h"
#endif
#ifdef OS_DETECTION_ENABLE
# include "os_detection.h"
#endif
static uint32_t last_input_modification_time = 0;
uint32_t last_input_activity_time(void) {
@ -741,4 +744,8 @@ void keyboard_task(void) {
#endif
led_task();
#ifdef OS_DETECTION_ENABLE
os_detection_task();
#endif
}

View File

@ -16,20 +16,25 @@
#include "os_detection.h"
#include <string.h>
#ifdef OS_DETECTION_DEBUG_ENABLE
# include "eeconfig.h"
# include "eeprom.h"
# include "print.h"
# define STORED_USB_SETUPS 50
# define EEPROM_USER_OFFSET (uint8_t*)EECONFIG_SIZE
uint16_t usb_setups[STORED_USB_SETUPS];
#endif
#ifdef OS_DETECTION_ENABLE
# include <string.h>
# include "timer.h"
# ifdef OS_DETECTION_KEYBOARD_RESET
# include "quantum.h"
# endif
# ifdef OS_DETECTION_DEBUG_ENABLE
# include "eeconfig.h"
# include "eeprom.h"
# include "print.h"
# define STORED_USB_SETUPS 50
# define EEPROM_USER_OFFSET (uint8_t*)EECONFIG_SIZE
static uint16_t usb_setups[STORED_USB_SETUPS];
# endif
struct setups_data_t {
uint8_t count;
uint8_t cnt_02;
@ -45,43 +50,63 @@ struct setups_data_t setups_data = {
.cnt_ff = 0,
};
os_variant_t detected_os = OS_UNSURE;
# ifndef OS_DETECTION_DEBOUNCE
# define OS_DETECTION_DEBOUNCE 200
# endif
// Some collected sequences of wLength can be found in tests.
void make_guess(void) {
if (setups_data.count < 3) {
return;
// 2s should always be more than enough (otherwise, you may have other issues)
# if OS_DETECTION_DEBOUNCE > 2000
# undef OS_DETECTION_DEBOUNCE
# define OS_DETECTION_DEBOUNCE 2000
# endif
typedef uint16_t debouncing_t;
static volatile os_variant_t detected_os = OS_UNSURE;
static os_variant_t reported_os = OS_UNSURE;
// we need to be able to report OS_UNSURE if that is the stable result of the guesses
static bool first_report = true;
// to react on USB state changes
static volatile enum usb_device_state current_usb_device_state = USB_DEVICE_STATE_INIT;
static enum usb_device_state reported_usb_device_state = USB_DEVICE_STATE_INIT;
// the OS detection might be unstable for a while, "debounce" it
static volatile bool debouncing = false;
static volatile fast_timer_t last_time;
void os_detection_task(void) {
if (current_usb_device_state == USB_DEVICE_STATE_CONFIGURED) {
// debouncing goes for both the detected OS as well as the USB state
if (debouncing && timer_elapsed_fast(last_time) >= OS_DETECTION_DEBOUNCE) {
debouncing = false;
reported_usb_device_state = current_usb_device_state;
if (detected_os != reported_os || first_report) {
first_report = false;
reported_os = detected_os;
process_detected_host_os_kb(detected_os);
}
}
}
if (setups_data.cnt_ff >= 2 && setups_data.cnt_04 >= 1) {
detected_os = OS_WINDOWS;
return;
}
if (setups_data.count == setups_data.cnt_ff) {
// Linux has 3 packets with 0xFF.
detected_os = OS_LINUX;
return;
}
if (setups_data.count == 5 && setups_data.last_wlength == 0xFF && setups_data.cnt_ff == 1 && setups_data.cnt_02 == 2) {
detected_os = OS_MACOS;
return;
}
if (setups_data.count == 4 && setups_data.cnt_ff == 0 && setups_data.cnt_02 == 2) {
// iOS and iPadOS don't have the last 0xFF packet.
detected_os = OS_IOS;
return;
}
if (setups_data.cnt_ff == 0 && setups_data.cnt_02 == 3 && setups_data.cnt_04 == 1) {
// This is actually PS5.
detected_os = OS_LINUX;
return;
}
if (setups_data.cnt_ff >= 1 && setups_data.cnt_02 == 0 && setups_data.cnt_04 == 0) {
// This is actually Quest 2 or Nintendo Switch.
detected_os = OS_LINUX;
return;
# ifdef OS_DETECTION_KEYBOARD_RESET
// resetting the keyboard on the USB device state change callback results in instability, so delegate that to this task
// only take action if it's been stable at least once, to avoid issues with some KVMs
else if (current_usb_device_state == USB_DEVICE_STATE_INIT && reported_usb_device_state != USB_DEVICE_STATE_INIT) {
soft_reset_keyboard();
}
# endif
}
__attribute__((weak)) bool process_detected_host_os_kb(os_variant_t detected_os) {
return process_detected_host_os_user(detected_os);
}
__attribute__((weak)) bool process_detected_host_os_user(os_variant_t detected_os) {
return true;
}
// Some collected sequences of wLength can be found in tests.
void process_wlength(const uint16_t w_length) {
# ifdef OS_DETECTION_DEBUG_ENABLE
usb_setups[setups_data.count] = w_length;
@ -95,7 +120,37 @@ void process_wlength(const uint16_t w_length) {
} else if (w_length == 0xFF) {
setups_data.cnt_ff++;
}
make_guess();
// now try to make a guess
os_variant_t guessed = OS_UNSURE;
if (setups_data.count >= 3) {
if (setups_data.cnt_ff >= 2 && setups_data.cnt_04 >= 1) {
guessed = OS_WINDOWS;
} else if (setups_data.count == setups_data.cnt_ff) {
// Linux has 3 packets with 0xFF.
guessed = OS_LINUX;
} else if (setups_data.count == 5 && setups_data.last_wlength == 0xFF && setups_data.cnt_ff == 1 && setups_data.cnt_02 == 2) {
guessed = OS_MACOS;
} else if (setups_data.count == 4 && setups_data.cnt_ff == 0 && setups_data.cnt_02 == 2) {
// iOS and iPadOS don't have the last 0xFF packet.
guessed = OS_IOS;
} else if (setups_data.cnt_ff == 0 && setups_data.cnt_02 == 3 && setups_data.cnt_04 == 1) {
// This is actually PS5.
guessed = OS_LINUX;
} else if (setups_data.cnt_ff >= 1 && setups_data.cnt_02 == 0 && setups_data.cnt_04 == 0) {
// This is actually Quest 2 or Nintendo Switch.
guessed = OS_LINUX;
}
}
// only replace the guessed value if not unsure
if (guessed != OS_UNSURE) {
detected_os = guessed;
}
// whatever the result, debounce
last_time = timer_read_fast();
debouncing = true;
}
os_variant_t detected_host_os(void) {
@ -104,25 +159,38 @@ os_variant_t detected_host_os(void) {
void erase_wlength_data(void) {
memset(&setups_data, 0, sizeof(setups_data));
detected_os = OS_UNSURE;
detected_os = OS_UNSURE;
reported_os = OS_UNSURE;
current_usb_device_state = USB_DEVICE_STATE_INIT;
reported_usb_device_state = USB_DEVICE_STATE_INIT;
debouncing = false;
first_report = true;
}
void os_detection_notify_usb_device_state_change(enum usb_device_state usb_device_state) {
// treat this like any other source of instability
current_usb_device_state = usb_device_state;
last_time = timer_read_fast();
debouncing = true;
}
# if defined(SPLIT_KEYBOARD) && defined(SPLIT_DETECTED_OS_ENABLE)
void slave_update_detected_host_os(os_variant_t os) {
detected_os = os;
last_time = timer_read_fast();
debouncing = true;
}
# endif // defined(SPLIT_KEYBOARD) && defined(SPLIT_DETECTED_OS_ENABLE)
#endif // OS_DETECTION_ENABLE
# endif
#ifdef OS_DETECTION_DEBUG_ENABLE
# ifdef OS_DETECTION_DEBUG_ENABLE
void print_stored_setups(void) {
# ifdef CONSOLE_ENABLE
# ifdef CONSOLE_ENABLE
uint8_t cnt = eeprom_read_byte(EEPROM_USER_OFFSET);
for (uint16_t i = 0; i < cnt; ++i) {
uint16_t* addr = (uint16_t*)EEPROM_USER_OFFSET + i * sizeof(uint16_t) + sizeof(uint8_t);
xprintf("i: %d, wLength: 0x%02X\n", i, eeprom_read_word(addr));
}
# endif
# endif
}
void store_setups_in_eeprom(void) {
@ -133,4 +201,6 @@ void store_setups_in_eeprom(void) {
}
}
#endif // OS_DETECTION_DEBUG_ENABLE
# endif // OS_DETECTION_DEBUG_ENABLE
#endif

View File

@ -16,9 +16,12 @@
#pragma once
#include <stdint.h>
#ifdef OS_DETECTION_ENABLE
# include <stdint.h>
# include <stdbool.h>
# include "usb_device_state.h"
typedef enum {
OS_UNSURE,
OS_LINUX,
@ -30,13 +33,20 @@ typedef enum {
void process_wlength(const uint16_t w_length);
os_variant_t detected_host_os(void);
void erase_wlength_data(void);
void os_detection_notify_usb_device_state_change(enum usb_device_state usb_device_state);
void os_detection_task(void);
bool process_detected_host_os_kb(os_variant_t os);
bool process_detected_host_os_user(os_variant_t os);
# if defined(SPLIT_KEYBOARD) && defined(SPLIT_DETECTED_OS_ENABLE)
void slave_update_detected_host_os(os_variant_t os);
# endif // defined(SPLIT_KEYBOARD) && defined(SPLIT_DETECTED_OS_ENABLE)
#endif
# endif
#ifdef OS_DETECTION_DEBUG_ENABLE
# ifdef OS_DETECTION_DEBUG_ENABLE
void print_stored_setups(void);
void store_setups_in_eeprom(void);
#endif
# endif
#endif // OS_DETECTION_ENABLE

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,7 @@
os_detection_DEFS := -DOS_DETECTION_ENABLE
os_detection_DEFS += -DOS_DETECTION_DEBOUNCE=50
os_detection_SRC := \
$(QUANTUM_PATH)/os_detection/tests/os_detection.cpp \
$(QUANTUM_PATH)/os_detection.c
$(QUANTUM_PATH)/os_detection.c \
$(PLATFORM_PATH)/$(PLATFORM_KEY)/timer.c

View File

@ -236,6 +236,10 @@ extern layer_state_t layer_state;
# include "process_repeat_key.h"
#endif
#ifdef OS_DETECTION_ENABLE
# include "os_detection.h"
#endif
void set_single_persistent_default_layer(uint8_t default_layer);
#define IS_LAYER_ON(layer) layer_state_is(layer)

View File

@ -20,6 +20,10 @@
# include "haptic.h"
#endif
#ifdef OS_DETECTION_ENABLE
# include "os_detection.h"
#endif
enum usb_device_state usb_device_state = USB_DEVICE_STATE_NO_INIT;
__attribute__((weak)) void notify_usb_device_state_change_kb(enum usb_device_state usb_device_state) {
@ -32,7 +36,12 @@ static void notify_usb_device_state_change(enum usb_device_state usb_device_stat
#if defined(HAPTIC_ENABLE) && HAPTIC_OFF_IN_LOW_POWER
haptic_notify_usb_device_state_change();
#endif
notify_usb_device_state_change_kb(usb_device_state);
#ifdef OS_DETECTION_ENABLE
os_detection_notify_usb_device_state_change(usb_device_state);
#endif
}
void usb_device_state_set_configuration(bool isConfigured, uint8_t configurationNumber) {