From 59d88cff55921a71c58927586d72b5faf6491a14 Mon Sep 17 00:00:00 2001 From: Ruslan Sayfutdinov Date: Mon, 5 Sep 2022 11:30:23 +0100 Subject: [PATCH 1/6] Detect host OS based on USB fingerprint --- builddefs/build_test.mk | 1 + builddefs/common_features.mk | 8 + builddefs/testlist.mk | 1 + docs/_summary.md | 1 + docs/feature_os_detection.md | 73 +++++++++ quantum/os_detection.c | 132 ++++++++++++++++ quantum/os_detection.h | 41 +++++ quantum/os_detection/tests/os_detection.cpp | 164 ++++++++++++++++++++ quantum/os_detection/tests/rules.mk | 5 + quantum/os_detection/tests/testlist.mk | 1 + tmk_core/protocol/chibios/usb_main.c | 10 +- tmk_core/protocol/lufa/lufa.c | 2 +- tmk_core/protocol/usb_descriptor.c | 9 +- tmk_core/protocol/usb_descriptor.h | 2 +- tmk_core/protocol/vusb/vusb.c | 7 + 15 files changed, 450 insertions(+), 7 deletions(-) create mode 100644 docs/feature_os_detection.md create mode 100644 quantum/os_detection.c create mode 100644 quantum/os_detection.h create mode 100644 quantum/os_detection/tests/os_detection.cpp create mode 100644 quantum/os_detection/tests/rules.mk create mode 100644 quantum/os_detection/tests/testlist.mk diff --git a/builddefs/build_test.mk b/builddefs/build_test.mk index 64db99fed910..bdcb656766eb 100644 --- a/builddefs/build_test.mk +++ b/builddefs/build_test.mk @@ -62,6 +62,7 @@ include $(PLATFORM_PATH)/common.mk include $(TMK_PATH)/protocol.mk include $(QUANTUM_PATH)/debounce/tests/rules.mk include $(QUANTUM_PATH)/encoder/tests/rules.mk +include $(QUANTUM_PATH)/os_detection/tests/rules.mk include $(QUANTUM_PATH)/sequencer/tests/rules.mk include $(QUANTUM_PATH)/wear_leveling/tests/rules.mk include $(QUANTUM_PATH)/logging/print.mk diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk index 3d34c673d3aa..94feebf19aa0 100644 --- a/builddefs/common_features.mk +++ b/builddefs/common_features.mk @@ -904,3 +904,11 @@ ifeq ($(strip $(ENCODER_ENABLE)), yes) OPT_DEFS += -DENCODER_MAP_ENABLE endif endif + +ifeq ($(strip $(OS_DETECTION_ENABLE)), yes) + SRC += $(QUANTUM_DIR)/os_detection.c + OPT_DEFS += -DOS_DETECTION_ENABLE + ifeq ($(strip $(OS_DETECTION_DEBUG_ENABLE)), yes) + OPT_DEFS += -DOS_DETECTION_DEBUG_ENABLE + endif +endif diff --git a/builddefs/testlist.mk b/builddefs/testlist.mk index 8a30a4497242..74a794adcdc9 100644 --- a/builddefs/testlist.mk +++ b/builddefs/testlist.mk @@ -3,6 +3,7 @@ FULL_TESTS := $(notdir $(TEST_LIST)) include $(QUANTUM_PATH)/debounce/tests/testlist.mk include $(QUANTUM_PATH)/encoder/tests/testlist.mk +include $(QUANTUM_PATH)/os_detection/tests/testlist.mk include $(QUANTUM_PATH)/sequencer/tests/testlist.mk include $(QUANTUM_PATH)/wear_leveling/tests/testlist.mk include $(PLATFORM_PATH)/test/testlist.mk diff --git a/docs/_summary.md b/docs/_summary.md index ca6fb91a79c7..73c487e1559c 100644 --- a/docs/_summary.md +++ b/docs/_summary.md @@ -85,6 +85,7 @@ * [Key Overrides](feature_key_overrides.md) * [Layers](feature_layers.md) * [One Shot Keys](one_shot_keys.md) + * [OS Detection](feature_os_detection.md) * [Raw HID](feature_rawhid.md) * [Secure](feature_secure.md) * [Send String](feature_send_string.md) diff --git a/docs/feature_os_detection.md b/docs/feature_os_detection.md new file mode 100644 index 000000000000..4b11b4955aa6 --- /dev/null +++ b/docs/feature_os_detection.md @@ -0,0 +1,73 @@ +# OS Detection + +This feature allows to detect host OS based on OS specific behaviour during USB setup. + +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: + +```make +OS_DETECTION_ENABLE = yes +``` + +Include `"os_detection.h"` in your `keymap.c`. +It declares `OSVariant detected_host_os(void);` which you can call to get detected OS. + +It returns one of the following values: + +```c +enum { + OS_UNSURE, + OS_LINUX, + OS_WINDOWS, + OS_MACOS, + OS_IOS, +} OSVariant; +``` + +?> 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. + +## 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 `rules.mk` add: + +```make +OS_DETECTION_DEBUG_ENABLE = yes +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`: + +```c +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. + diff --git a/quantum/os_detection.c b/quantum/os_detection.c new file mode 100644 index 000000000000..6dd189180312 --- /dev/null +++ b/quantum/os_detection.c @@ -0,0 +1,132 @@ +/* Copyright 2022 Ruslan Sayfutdinov (@KapJI) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "os_detection.h" + +#ifdef OS_DETECTION_TESTS +# include +#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 + +uint16_t usb_setups[STORED_USB_SETUPS]; +#endif + +#ifdef OS_DETECTION_ENABLE +struct setups_data_t { + uint8_t count; + uint8_t cnt_02; + uint8_t cnt_04; + uint8_t cnt_ff; + uint16_t last_wlength; + OSVariant detected_os; +}; + +struct setups_data_t setups_data = { + .count = 0, + .cnt_02 = 0, + .cnt_04 = 0, + .cnt_ff = 0, + .detected_os = OS_UNSURE, +}; + +// Some collected sequences of wLength can be found in tests. +void make_guess(void) { + if (setups_data.count < 3) { + return; + } + if (setups_data.cnt_ff >= 2 && setups_data.cnt_04 >= 1) { + setups_data.detected_os = OS_WINDOWS; + return; + } + if (setups_data.count == setups_data.cnt_ff) { + // Linux has 3 packets with 0xFF. + setups_data.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) { + setups_data.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. + setups_data.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. + setups_data.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. + setups_data.detected_os = OS_LINUX; + return; + } +} + +void process_wlength(const uint16_t w_length) { +# ifdef OS_DETECTION_DEBUG_ENABLE + usb_setups[setups_data.count] = w_length; +# endif + setups_data.count++; + setups_data.last_wlength = w_length; + if (w_length == 0x2) { + setups_data.cnt_02++; + } else if (w_length == 0x4) { + setups_data.cnt_04++; + } else if (w_length == 0xFF) { + setups_data.cnt_ff++; + } + make_guess(); +} + +OSVariant detected_host_os(void) { + return setups_data.detected_os; +} +#endif // OS_DETECTION_ENABLE + +#ifdef OS_DETECTION_DEBUG_ENABLE +void print_stored_setups(void) { +# ifdef CONSOLE_ENABLE + uint8_t cnt = eeprom_read_byte(EEPROM_USER_OFFSET); + for (uint16_t i = 0; i < cnt; ++i) { + uint16_t* addr = EEPROM_USER_OFFSET + i * sizeof(uint16_t) + sizeof(uint8_t); + uprintf("i: %d, wLength: %X\n", i, eeprom_read_word(addr)); + } +# endif +} + +void store_setups_in_eeprom(void) { + eeprom_update_byte(EEPROM_USER_OFFSET, setups_data.count); + for (uint16_t i = 0; i < setups_data.count; ++i) { + uint16_t* addr = EEPROM_USER_OFFSET + i * sizeof(uint16_t) + sizeof(uint8_t); + eeprom_update_word(addr, usb_setups[i]); + } +} +#endif // OS_DETECTION_DEBUG_ENABLE + +#ifdef OS_DETECTION_TESTS +void clean_wlength_data(void) { + memset(&setups_data, 0, sizeof(setups_data)); +} +#endif diff --git a/quantum/os_detection.h b/quantum/os_detection.h new file mode 100644 index 000000000000..6b2a0df82e24 --- /dev/null +++ b/quantum/os_detection.h @@ -0,0 +1,41 @@ +/* Copyright 2022 Ruslan Sayfutdinov (@KapJI) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +#ifdef OS_DETECTION_ENABLE +typedef enum { + OS_UNSURE, + OS_LINUX, + OS_WINDOWS, + OS_MACOS, + OS_IOS, +} OSVariant; + +void process_wlength(const uint16_t w_length); +OSVariant detected_host_os(void); +#endif + +#ifdef OS_DETECTION_DEBUG_ENABLE +void print_stored_setups(void); +void store_setups_in_eeprom(void); +#endif + +#ifdef OS_DETECTION_TESTS +void clean_wlength_data(void); +#endif diff --git a/quantum/os_detection/tests/os_detection.cpp b/quantum/os_detection/tests/os_detection.cpp new file mode 100644 index 000000000000..a0c4db4b20df --- /dev/null +++ b/quantum/os_detection/tests/os_detection.cpp @@ -0,0 +1,164 @@ +/* Copyright 2022 Ruslan Sayfutdinov (@KapJI) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gtest/gtest.h" + +extern "C" { +#include "os_detection.h" +} + +class OsDetectionTest : public ::testing::Test { + protected: + void SetUp() override { + clean_wlength_data(); + } +}; + +OSVariant check_sequence(const std::vector &w_lengths) { + for (auto &w_length : w_lengths) { + process_wlength(w_length); + } + return detected_host_os(); +} + +/* Some collected data. + +ChibiOS: +Windows 10: [FF, FF, 4, 24, 4, 24, 4, FF, 24, FF, 4, FF, 24, 4, 24, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A] +Windows 10 (another host): [FF, FF, 4, 24, 4, 24, 4, 24, 4, 24, 4, 24] +macOS 12.5: [2, 24, 2, 28, FF] +iOS/iPadOS 15.6: [2, 24, 2, 28] +Linux (including Android, Raspberry Pi and WebOS TV): [FF, FF, FF] +PS5: [2, 4, 2, 28, 2, 24] +Nintendo Switch: [82, FF, 40, 40, FF, 40, 40, FF, 40, 40, FF, 40, 40, FF, 40, 40] +Quest 2: [FF, FF, FF, FE, FF, FE, FF, FE, FF, FE, FF] + +LUFA: +Windows 10 (first connect): [12, FF, FF, 4, 10, FF, FF, FF, 4, 10, 20A, 20A, 20A, 20A, 20A, 20A] +Windows 10 (subsequent connect): [FF, FF, 4, 10, FF, 4, FF, 10, FF, 20A, 20A, 20A, 20A, 20A, 20A] +Windows 10 (another host): [FF, FF, 4, 10, 4, 10] +macOS: [2, 10, 2, E, FF] +iOS/iPadOS: [2, 10, 2, E] +Linux: [FF, FF, FF] +PS5: [2, 4, 2, E, 2, 10] +Nintendo Switch: [82, FF, 40, 40, FF, 40, 40] + +V-USB: +Windows 10: [FF, FF, 4, E, FF] +Windows 10 (another host): [FF, FF, 4, E, 4] +macOS: [2, E, 2, E, FF] +iOS/iPadOS: [2, E, 2, E] +Linux: [FF, FF, FF] +PS5: [2, 4, 2, E, 2] +Nintendo Switch: [82, FF, 40, 40] +Quest 2: [FF, FF, FF, FE] + +Common parts: +Windows: [..., FF, FF, 4, ...] +macOS: [2, _, 2, _, FF] +iOS/iPadOS: [2, _, 2, _] +Linux: [FF, FF, FF] +PS5: [2, 4, 2, _, 2, ...] +Nintendo Switch: [82, FF, 40, 40, ...] +Quest 2: [FF, FF, FF, FE, ...] +*/ +TEST_F(OsDetectionTest, TestLinux) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0xFF}), OS_LINUX); +} + +TEST_F(OsDetectionTest, TestChibiosMacos) { + EXPECT_EQ(check_sequence({0x2, 0x24, 0x2, 0x28, 0xFF}), OS_MACOS); +} + +TEST_F(OsDetectionTest, TestLufaMacos) { + EXPECT_EQ(check_sequence({0x2, 0x10, 0x2, 0xE, 0xFF}), OS_MACOS); +} + +TEST_F(OsDetectionTest, TestVusbMacos) { + EXPECT_EQ(check_sequence({0x2, 0xE, 0x2, 0xE, 0xFF}), OS_MACOS); +} + +TEST_F(OsDetectionTest, TestChibiosIos) { + EXPECT_EQ(check_sequence({0x2, 0x24, 0x2, 0x28}), OS_IOS); +} + +TEST_F(OsDetectionTest, TestLufaIos) { + EXPECT_EQ(check_sequence({0x2, 0x10, 0x2, 0xE}), OS_IOS); +} + +TEST_F(OsDetectionTest, TestVusbIos) { + EXPECT_EQ(check_sequence({0x2, 0xE, 0x2, 0xE}), OS_IOS); +} + +TEST_F(OsDetectionTest, TestChibiosWindows10) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0x24, 0x4, 0x24, 0x4, 0xFF, 0x24, 0xFF, 0x4, 0xFF, 0x24, 0x4, 0x24, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A}), OS_WINDOWS); +} + +TEST_F(OsDetectionTest, TestChibiosWindows10_2) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0x24, 0x4, 0x24, 0x4, 0x24, 0x4, 0x24, 0x4, 0x24}), OS_WINDOWS); +} + +TEST_F(OsDetectionTest, TestLufaWindows10) { + EXPECT_EQ(check_sequence({0x12, 0xFF, 0xFF, 0x4, 0x10, 0xFF, 0xFF, 0xFF, 0x4, 0x10, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A}), OS_WINDOWS); +} + +TEST_F(OsDetectionTest, TestLufaWindows10_2) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0x10, 0xFF, 0x4, 0xFF, 0x10, 0xFF, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A}), OS_WINDOWS); +} + +TEST_F(OsDetectionTest, TestLufaWindows10_3) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0x10, 0x4, 0x10}), OS_WINDOWS); +} + +TEST_F(OsDetectionTest, TestVusbWindows10) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0xE, 0xFF}), OS_WINDOWS); +} + +TEST_F(OsDetectionTest, TestVusbWindows10_2) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0xE, 0x4}), OS_WINDOWS); +} + +TEST_F(OsDetectionTest, TestChibiosPs5) { + EXPECT_EQ(check_sequence({0x2, 0x4, 0x2, 0x28, 0x2, 0x24}), OS_LINUX); +} + +TEST_F(OsDetectionTest, TestLufaPs5) { + EXPECT_EQ(check_sequence({0x2, 0x4, 0x2, 0xE, 0x2, 0x10}), OS_LINUX); +} + +TEST_F(OsDetectionTest, TestVusbPs5) { + EXPECT_EQ(check_sequence({0x2, 0x4, 0x2, 0xE, 0x2}), OS_LINUX); +} + +TEST_F(OsDetectionTest, TestChibiosNintendoSwitch) { + EXPECT_EQ(check_sequence({0x82, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40}), OS_LINUX); +} + +TEST_F(OsDetectionTest, TestLufaNintendoSwitch) { + EXPECT_EQ(check_sequence({0x82, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40}), OS_LINUX); +} + +TEST_F(OsDetectionTest, TestVusbNintendoSwitch) { + EXPECT_EQ(check_sequence({0x82, 0xFF, 0x40, 0x40}), OS_LINUX); +} + +TEST_F(OsDetectionTest, TestChibiosQuest2) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF}), OS_LINUX); +} + +TEST_F(OsDetectionTest, TestVusbQuest2) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0xFF, 0xFE}), OS_LINUX); +} diff --git a/quantum/os_detection/tests/rules.mk b/quantum/os_detection/tests/rules.mk new file mode 100644 index 000000000000..85c6c67274b4 --- /dev/null +++ b/quantum/os_detection/tests/rules.mk @@ -0,0 +1,5 @@ +os_detection_DEFS := -DOS_DETECTION_ENABLE -DOS_DETECTION_TESTS + +os_detection_SRC := \ + $(QUANTUM_PATH)/os_detection/tests/os_detection.cpp \ + $(QUANTUM_PATH)/os_detection.c diff --git a/quantum/os_detection/tests/testlist.mk b/quantum/os_detection/tests/testlist.mk new file mode 100644 index 000000000000..405a7b82d55b --- /dev/null +++ b/quantum/os_detection/tests/testlist.mk @@ -0,0 +1 @@ +TEST_LIST += os_detection diff --git a/tmk_core/protocol/chibios/usb_main.c b/tmk_core/protocol/chibios/usb_main.c index 222a867e3c7e..2abd9036621e 100644 --- a/tmk_core/protocol/chibios/usb_main.c +++ b/tmk_core/protocol/chibios/usb_main.c @@ -115,9 +115,10 @@ uint8_t extra_report_blank[3] = {0}; static const USBDescriptor *usb_get_descriptor_cb(USBDriver *usbp, uint8_t dtype, uint8_t dindex, uint16_t wIndex) { (void)usbp; static USBDescriptor desc; - uint16_t wValue = ((uint16_t)dtype << 8) | dindex; - desc.ud_string = NULL; - desc.ud_size = get_usb_descriptor(wValue, wIndex, (const void **const) & desc.ud_string); + uint16_t wValue = ((uint16_t)dtype << 8) | dindex; + uint16_t wLength = ((uint16_t)usbp->setup[7] << 8) | usbp->setup[6]; + desc.ud_string = NULL; + desc.ud_size = get_usb_descriptor(wValue, wIndex, (const void **const)&desc.ud_string); if (desc.ud_string == NULL) return NULL; else @@ -559,7 +560,8 @@ static uint16_t get_hword(uint8_t *p) { */ static uint8_t set_report_buf[2] __attribute__((aligned(4))); -static void set_led_transfer_cb(USBDriver *usbp) { + +static void set_led_transfer_cb(USBDriver *usbp) { if (usbp->setup[6] == 2) { /* LSB(wLength) */ uint8_t report_id = set_report_buf[0]; if ((report_id == REPORT_ID_KEYBOARD) || (report_id == REPORT_ID_NKRO)) { diff --git a/tmk_core/protocol/lufa/lufa.c b/tmk_core/protocol/lufa/lufa.c index 2a3f5fd8839b..b398c44eeee6 100644 --- a/tmk_core/protocol/lufa/lufa.c +++ b/tmk_core/protocol/lufa/lufa.c @@ -1030,5 +1030,5 @@ void protocol_post_task(void) { } uint16_t CALLBACK_USB_GetDescriptor(const uint16_t wValue, const uint16_t wIndex, const void **const DescriptorAddress) { - return get_usb_descriptor(wValue, wIndex, DescriptorAddress); + return get_usb_descriptor(wValue, wIndex, USB_ControlRequest.wLength, DescriptorAddress); } diff --git a/tmk_core/protocol/usb_descriptor.c b/tmk_core/protocol/usb_descriptor.c index 7117d2fc1134..2f8174bf8699 100644 --- a/tmk_core/protocol/usb_descriptor.c +++ b/tmk_core/protocol/usb_descriptor.c @@ -45,6 +45,10 @@ # include "joystick.h" #endif +#ifdef OS_DETECTION_ENABLE +# include "os_detection.h" +#endif + // TODO: wb32 support defines ISO macro which breaks PRODUCT stringification #undef ISO @@ -1087,7 +1091,7 @@ const USB_Descriptor_String_t PROGMEM SerialNumberString = { * is called so that the descriptor details can be passed back and the appropriate descriptor sent back to the * USB host. */ -uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const void** const DescriptorAddress) { +uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const uint16_t wLength, const void** const DescriptorAddress) { const uint8_t DescriptorType = (wValue >> 8); const uint8_t DescriptorIndex = (wValue & 0xFF); const void* Address = NULL; @@ -1129,6 +1133,9 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const break; #endif } +#ifdef OS_DETECTION_ENABLE + process_wlength(wLength); +#endif break; case HID_DTYPE_HID: diff --git a/tmk_core/protocol/usb_descriptor.h b/tmk_core/protocol/usb_descriptor.h index f8b7a863aac0..31476c4f93e0 100644 --- a/tmk_core/protocol/usb_descriptor.h +++ b/tmk_core/protocol/usb_descriptor.h @@ -313,4 +313,4 @@ enum usb_endpoints { #define JOYSTICK_EPSIZE 8 #define DIGITIZER_EPSIZE 8 -uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const void** const DescriptorAddress); +uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const uint16_t wLength, const void** const DescriptorAddress); diff --git a/tmk_core/protocol/vusb/vusb.c b/tmk_core/protocol/vusb/vusb.c index 013d637b6fc3..4e66100f365c 100644 --- a/tmk_core/protocol/vusb/vusb.c +++ b/tmk_core/protocol/vusb/vusb.c @@ -40,6 +40,10 @@ along with this program. If not, see . # include "ring_buffer.h" #endif +#ifdef OS_DETECTION_ENABLE +# include "os_detection.h" +#endif + #define NEXT_INTERFACE __COUNTER__ /* @@ -959,6 +963,9 @@ USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) { break; #endif } +#ifdef OS_DETECTION_ENABLE + process_wlength(rq->wLength.word); +#endif break; case USBDESCR_HID: switch (rq->wValue.bytes[0]) { From 792c9a58e6d5991108d5743b8d2801c004ae80e7 Mon Sep 17 00:00:00 2001 From: Ruslan Sayfutdinov Date: Sat, 24 Sep 2022 19:26:33 +0100 Subject: [PATCH 2/6] Apply suggestions --- docs/feature_os_detection.md | 4 ++-- quantum/os_detection.c | 25 +++++++++------------ quantum/os_detection.h | 9 +++----- quantum/os_detection/tests/os_detection.cpp | 4 ++-- quantum/os_detection/tests/rules.mk | 2 +- tmk_core/protocol/chibios/usb_main.c | 2 +- 6 files changed, 20 insertions(+), 26 deletions(-) diff --git a/docs/feature_os_detection.md b/docs/feature_os_detection.md index 4b11b4955aa6..691f99533749 100644 --- a/docs/feature_os_detection.md +++ b/docs/feature_os_detection.md @@ -15,7 +15,7 @@ OS_DETECTION_ENABLE = yes ``` Include `"os_detection.h"` in your `keymap.c`. -It declares `OSVariant detected_host_os(void);` which you can call to get detected OS. +It declares `os_variant_t detected_host_os(void);` which you can call to get detected OS. It returns one of the following values: @@ -26,7 +26,7 @@ enum { OS_WINDOWS, OS_MACOS, OS_IOS, -} OSVariant; +} os_variant_t; ``` ?> Note that it takes some time after firmware is booted to detect the OS. diff --git a/quantum/os_detection.c b/quantum/os_detection.c index 6dd189180312..33d1b301a935 100644 --- a/quantum/os_detection.c +++ b/quantum/os_detection.c @@ -16,9 +16,7 @@ #include "os_detection.h" -#ifdef OS_DETECTION_TESTS -# include -#endif +#include #ifdef OS_DETECTION_DEBUG_ENABLE # include "eeconfig.h" @@ -38,7 +36,7 @@ struct setups_data_t { uint8_t cnt_04; uint8_t cnt_ff; uint16_t last_wlength; - OSVariant detected_os; + os_variant_t detected_os; }; struct setups_data_t setups_data = { @@ -100,9 +98,13 @@ void process_wlength(const uint16_t w_length) { make_guess(); } -OSVariant detected_host_os(void) { +os_variant_t detected_host_os(void) { return setups_data.detected_os; } + +void erase_wlength_data(void) { + memset(&setups_data, 0, sizeof(setups_data)); +} #endif // OS_DETECTION_ENABLE #ifdef OS_DETECTION_DEBUG_ENABLE @@ -110,8 +112,8 @@ void print_stored_setups(void) { # ifdef CONSOLE_ENABLE uint8_t cnt = eeprom_read_byte(EEPROM_USER_OFFSET); for (uint16_t i = 0; i < cnt; ++i) { - uint16_t* addr = EEPROM_USER_OFFSET + i * sizeof(uint16_t) + sizeof(uint8_t); - uprintf("i: %d, wLength: %X\n", i, eeprom_read_word(addr)); + 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 } @@ -119,14 +121,9 @@ void print_stored_setups(void) { void store_setups_in_eeprom(void) { eeprom_update_byte(EEPROM_USER_OFFSET, setups_data.count); for (uint16_t i = 0; i < setups_data.count; ++i) { - uint16_t* addr = EEPROM_USER_OFFSET + i * sizeof(uint16_t) + sizeof(uint8_t); + uint16_t* addr = (uint16_t*)EEPROM_USER_OFFSET + i * sizeof(uint16_t) + sizeof(uint8_t); eeprom_update_word(addr, usb_setups[i]); } } -#endif // OS_DETECTION_DEBUG_ENABLE -#ifdef OS_DETECTION_TESTS -void clean_wlength_data(void) { - memset(&setups_data, 0, sizeof(setups_data)); -} -#endif +#endif // OS_DETECTION_DEBUG_ENABLE diff --git a/quantum/os_detection.h b/quantum/os_detection.h index 6b2a0df82e24..fcc0279b3a72 100644 --- a/quantum/os_detection.h +++ b/quantum/os_detection.h @@ -25,17 +25,14 @@ typedef enum { OS_WINDOWS, OS_MACOS, OS_IOS, -} OSVariant; +} os_variant_t; void process_wlength(const uint16_t w_length); -OSVariant detected_host_os(void); +os_variant_t detected_host_os(void); +void erase_wlength_data(void); #endif #ifdef OS_DETECTION_DEBUG_ENABLE void print_stored_setups(void); void store_setups_in_eeprom(void); #endif - -#ifdef OS_DETECTION_TESTS -void clean_wlength_data(void); -#endif diff --git a/quantum/os_detection/tests/os_detection.cpp b/quantum/os_detection/tests/os_detection.cpp index a0c4db4b20df..102349852e0c 100644 --- a/quantum/os_detection/tests/os_detection.cpp +++ b/quantum/os_detection/tests/os_detection.cpp @@ -23,11 +23,11 @@ extern "C" { class OsDetectionTest : public ::testing::Test { protected: void SetUp() override { - clean_wlength_data(); + erase_wlength_data(); } }; -OSVariant check_sequence(const std::vector &w_lengths) { +os_variant_t check_sequence(const std::vector &w_lengths) { for (auto &w_length : w_lengths) { process_wlength(w_length); } diff --git a/quantum/os_detection/tests/rules.mk b/quantum/os_detection/tests/rules.mk index 85c6c67274b4..9bfe373f46ef 100644 --- a/quantum/os_detection/tests/rules.mk +++ b/quantum/os_detection/tests/rules.mk @@ -1,4 +1,4 @@ -os_detection_DEFS := -DOS_DETECTION_ENABLE -DOS_DETECTION_TESTS +os_detection_DEFS := -DOS_DETECTION_ENABLE os_detection_SRC := \ $(QUANTUM_PATH)/os_detection/tests/os_detection.cpp \ diff --git a/tmk_core/protocol/chibios/usb_main.c b/tmk_core/protocol/chibios/usb_main.c index 2abd9036621e..87fe7c883f1c 100644 --- a/tmk_core/protocol/chibios/usb_main.c +++ b/tmk_core/protocol/chibios/usb_main.c @@ -118,7 +118,7 @@ static const USBDescriptor *usb_get_descriptor_cb(USBDriver *usbp, uint8_t dtype uint16_t wValue = ((uint16_t)dtype << 8) | dindex; uint16_t wLength = ((uint16_t)usbp->setup[7] << 8) | usbp->setup[6]; desc.ud_string = NULL; - desc.ud_size = get_usb_descriptor(wValue, wIndex, (const void **const)&desc.ud_string); + desc.ud_size = get_usb_descriptor(wValue, wIndex, wLength, (const void **const) &desc.ud_string); if (desc.ud_string == NULL) return NULL; else From 9327bd0675f9e33e6e129ce484e5b0b34f10b96a Mon Sep 17 00:00:00 2001 From: Ruslan Sayfutdinov Date: Sun, 25 Sep 2022 21:25:55 +0100 Subject: [PATCH 3/6] Fix formatting --- quantum/os_detection.c | 10 +++++----- quantum/os_detection.h | 4 ++-- tmk_core/protocol/chibios/usb_main.c | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/quantum/os_detection.c b/quantum/os_detection.c index 33d1b301a935..b1511afb149a 100644 --- a/quantum/os_detection.c +++ b/quantum/os_detection.c @@ -31,11 +31,11 @@ uint16_t usb_setups[STORED_USB_SETUPS]; #ifdef OS_DETECTION_ENABLE struct setups_data_t { - uint8_t count; - uint8_t cnt_02; - uint8_t cnt_04; - uint8_t cnt_ff; - uint16_t last_wlength; + uint8_t count; + uint8_t cnt_02; + uint8_t cnt_04; + uint8_t cnt_ff; + uint16_t last_wlength; os_variant_t detected_os; }; diff --git a/quantum/os_detection.h b/quantum/os_detection.h index fcc0279b3a72..e643dcd27fab 100644 --- a/quantum/os_detection.h +++ b/quantum/os_detection.h @@ -27,9 +27,9 @@ typedef enum { OS_IOS, } os_variant_t; -void process_wlength(const uint16_t w_length); +void process_wlength(const uint16_t w_length); os_variant_t detected_host_os(void); -void erase_wlength_data(void); +void erase_wlength_data(void); #endif #ifdef OS_DETECTION_DEBUG_ENABLE diff --git a/tmk_core/protocol/chibios/usb_main.c b/tmk_core/protocol/chibios/usb_main.c index 87fe7c883f1c..d051d3a33653 100644 --- a/tmk_core/protocol/chibios/usb_main.c +++ b/tmk_core/protocol/chibios/usb_main.c @@ -118,7 +118,7 @@ static const USBDescriptor *usb_get_descriptor_cb(USBDriver *usbp, uint8_t dtype uint16_t wValue = ((uint16_t)dtype << 8) | dindex; uint16_t wLength = ((uint16_t)usbp->setup[7] << 8) | usbp->setup[6]; desc.ud_string = NULL; - desc.ud_size = get_usb_descriptor(wValue, wIndex, wLength, (const void **const) &desc.ud_string); + desc.ud_size = get_usb_descriptor(wValue, wIndex, wLength, (const void **const) & desc.ud_string); if (desc.ud_string == NULL) return NULL; else From 0e37c426c73dfca15e44be6e1d0cb31476183281 Mon Sep 17 00:00:00 2001 From: Ruslan Sayfutdinov Date: Sun, 25 Sep 2022 21:28:46 +0100 Subject: [PATCH 4/6] Mention FingerprintUSBHost --- docs/feature_os_detection.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/feature_os_detection.md b/docs/feature_os_detection.md index 691f99533749..f66cf875831b 100644 --- a/docs/feature_os_detection.md +++ b/docs/feature_os_detection.md @@ -71,3 +71,7 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { 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](https://github.com/keyboardio/FingerprintUSBHost) project. From 27f3da94b95052ac9084a341aea1d57bb07df200 Mon Sep 17 00:00:00 2001 From: Ruslan Sayfutdinov Date: Fri, 30 Sep 2022 20:01:37 +0100 Subject: [PATCH 5/6] Update docs/feature_os_detection.md Co-authored-by: Drashna Jaelre --- docs/feature_os_detection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/feature_os_detection.md b/docs/feature_os_detection.md index f66cf875831b..f32e419807f7 100644 --- a/docs/feature_os_detection.md +++ b/docs/feature_os_detection.md @@ -1,6 +1,6 @@ # OS Detection -This feature allows to detect host OS based on OS specific behaviour during USB setup. +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. From 1a6e900207ca08b453ad59d9bbbc0b3d624c220f Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Fri, 9 Dec 2022 02:44:00 +1100 Subject: [PATCH 6/6] Thanks, github UI. --- tmk_core/protocol/usb_descriptor.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tmk_core/protocol/usb_descriptor.c b/tmk_core/protocol/usb_descriptor.c index 76da8d12196b..5ab9e3ff4f56 100644 --- a/tmk_core/protocol/usb_descriptor.c +++ b/tmk_core/protocol/usb_descriptor.c @@ -45,16 +45,10 @@ # include "joystick.h" #endif -<<<<<<< guess-os -- Incoming Change #ifdef OS_DETECTION_ENABLE # include "os_detection.h" #endif -// TODO: wb32 support defines ISO macro which breaks PRODUCT stringification -#undef ISO - -======= ->>>>>>> develop -- Current Change // clang-format off /*