Detect host OS based on USB fingerprint (#18463)
Co-authored-by: Drashna Jaelre <drashna@live.com> Co-authored-by: Nick Brassel <nick@tzarc.org>
This commit is contained in:
parent
e06f50c489
commit
85ee55ff3b
|
@ -62,6 +62,7 @@ include $(PLATFORM_PATH)/common.mk
|
||||||
include $(TMK_PATH)/protocol.mk
|
include $(TMK_PATH)/protocol.mk
|
||||||
include $(QUANTUM_PATH)/debounce/tests/rules.mk
|
include $(QUANTUM_PATH)/debounce/tests/rules.mk
|
||||||
include $(QUANTUM_PATH)/encoder/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)/sequencer/tests/rules.mk
|
||||||
include $(QUANTUM_PATH)/wear_leveling/tests/rules.mk
|
include $(QUANTUM_PATH)/wear_leveling/tests/rules.mk
|
||||||
include $(QUANTUM_PATH)/logging/print.mk
|
include $(QUANTUM_PATH)/logging/print.mk
|
||||||
|
|
|
@ -907,3 +907,11 @@ ifeq ($(strip $(ENCODER_ENABLE)), yes)
|
||||||
OPT_DEFS += -DENCODER_MAP_ENABLE
|
OPT_DEFS += -DENCODER_MAP_ENABLE
|
||||||
endif
|
endif
|
||||||
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
|
||||||
|
|
|
@ -3,6 +3,7 @@ FULL_TESTS := $(notdir $(TEST_LIST))
|
||||||
|
|
||||||
include $(QUANTUM_PATH)/debounce/tests/testlist.mk
|
include $(QUANTUM_PATH)/debounce/tests/testlist.mk
|
||||||
include $(QUANTUM_PATH)/encoder/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)/sequencer/tests/testlist.mk
|
||||||
include $(QUANTUM_PATH)/wear_leveling/tests/testlist.mk
|
include $(QUANTUM_PATH)/wear_leveling/tests/testlist.mk
|
||||||
include $(PLATFORM_PATH)/test/testlist.mk
|
include $(PLATFORM_PATH)/test/testlist.mk
|
||||||
|
|
|
@ -85,6 +85,7 @@
|
||||||
* [Key Overrides](feature_key_overrides.md)
|
* [Key Overrides](feature_key_overrides.md)
|
||||||
* [Layers](feature_layers.md)
|
* [Layers](feature_layers.md)
|
||||||
* [One Shot Keys](one_shot_keys.md)
|
* [One Shot Keys](one_shot_keys.md)
|
||||||
|
* [OS Detection](feature_os_detection.md)
|
||||||
* [Raw HID](feature_rawhid.md)
|
* [Raw HID](feature_rawhid.md)
|
||||||
* [Secure](feature_secure.md)
|
* [Secure](feature_secure.md)
|
||||||
* [Send String](feature_send_string.md)
|
* [Send String](feature_send_string.md)
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
# 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:
|
||||||
|
|
||||||
|
```make
|
||||||
|
OS_DETECTION_ENABLE = yes
|
||||||
|
```
|
||||||
|
|
||||||
|
Include `"os_detection.h"` in your `keymap.c`.
|
||||||
|
It declares `os_variant_t 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,
|
||||||
|
} os_variant_t;
|
||||||
|
```
|
||||||
|
|
||||||
|
?> 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.
|
||||||
|
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
Original idea is coming from [FingerprintUSBHost](https://github.com/keyboardio/FingerprintUSBHost) project.
|
|
@ -0,0 +1,129 @@
|
||||||
|
/* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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
|
||||||
|
struct setups_data_t {
|
||||||
|
uint8_t count;
|
||||||
|
uint8_t cnt_02;
|
||||||
|
uint8_t cnt_04;
|
||||||
|
uint8_t cnt_ff;
|
||||||
|
uint16_t last_wlength;
|
||||||
|
os_variant_t 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
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 = (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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = (uint16_t*)EEPROM_USER_OFFSET + i * sizeof(uint16_t) + sizeof(uint8_t);
|
||||||
|
eeprom_update_word(addr, usb_setups[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // OS_DETECTION_DEBUG_ENABLE
|
|
@ -0,0 +1,38 @@
|
||||||
|
/* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef OS_DETECTION_ENABLE
|
||||||
|
typedef enum {
|
||||||
|
OS_UNSURE,
|
||||||
|
OS_LINUX,
|
||||||
|
OS_WINDOWS,
|
||||||
|
OS_MACOS,
|
||||||
|
OS_IOS,
|
||||||
|
} os_variant_t;
|
||||||
|
|
||||||
|
void process_wlength(const uint16_t w_length);
|
||||||
|
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
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "os_detection.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
class OsDetectionTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
erase_wlength_data();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
os_variant_t check_sequence(const std::vector<uint16_t> &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);
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
os_detection_DEFS := -DOS_DETECTION_ENABLE
|
||||||
|
|
||||||
|
os_detection_SRC := \
|
||||||
|
$(QUANTUM_PATH)/os_detection/tests/os_detection.cpp \
|
||||||
|
$(QUANTUM_PATH)/os_detection.c
|
|
@ -0,0 +1 @@
|
||||||
|
TEST_LIST += os_detection
|
|
@ -111,9 +111,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) {
|
static const USBDescriptor *usb_get_descriptor_cb(USBDriver *usbp, uint8_t dtype, uint8_t dindex, uint16_t wIndex) {
|
||||||
(void)usbp;
|
(void)usbp;
|
||||||
static USBDescriptor desc;
|
static USBDescriptor desc;
|
||||||
uint16_t wValue = ((uint16_t)dtype << 8) | dindex;
|
uint16_t wValue = ((uint16_t)dtype << 8) | dindex;
|
||||||
desc.ud_string = NULL;
|
uint16_t wLength = ((uint16_t)usbp->setup[7] << 8) | usbp->setup[6];
|
||||||
desc.ud_size = get_usb_descriptor(wValue, wIndex, (const void **const) & desc.ud_string);
|
desc.ud_string = NULL;
|
||||||
|
desc.ud_size = get_usb_descriptor(wValue, wIndex, wLength, (const void **const) & desc.ud_string);
|
||||||
if (desc.ud_string == NULL)
|
if (desc.ud_string == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
else
|
else
|
||||||
|
@ -585,7 +586,8 @@ static uint16_t get_hword(uint8_t *p) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static uint8_t set_report_buf[2] __attribute__((aligned(4)));
|
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) */
|
if (usbp->setup[6] == 2) { /* LSB(wLength) */
|
||||||
uint8_t report_id = set_report_buf[0];
|
uint8_t report_id = set_report_buf[0];
|
||||||
if ((report_id == REPORT_ID_KEYBOARD) || (report_id == REPORT_ID_NKRO)) {
|
if ((report_id == REPORT_ID_KEYBOARD) || (report_id == REPORT_ID_NKRO)) {
|
||||||
|
|
|
@ -901,5 +901,5 @@ void protocol_post_task(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t CALLBACK_USB_GetDescriptor(const uint16_t wValue, const uint16_t wIndex, const void **const DescriptorAddress) {
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,10 @@
|
||||||
# include "joystick.h"
|
# include "joystick.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef OS_DETECTION_ENABLE
|
||||||
|
# include "os_detection.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1092,7 +1096,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
|
* is called so that the descriptor details can be passed back and the appropriate descriptor sent back to the
|
||||||
* USB host.
|
* 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 DescriptorType = (wValue >> 8);
|
||||||
const uint8_t DescriptorIndex = (wValue & 0xFF);
|
const uint8_t DescriptorIndex = (wValue & 0xFF);
|
||||||
const void* Address = NULL;
|
const void* Address = NULL;
|
||||||
|
@ -1134,6 +1138,9 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
#ifdef OS_DETECTION_ENABLE
|
||||||
|
process_wlength(wLength);
|
||||||
|
#endif
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case HID_DTYPE_HID:
|
case HID_DTYPE_HID:
|
||||||
|
|
|
@ -308,4 +308,4 @@ enum usb_endpoints {
|
||||||
#define JOYSTICK_EPSIZE 8
|
#define JOYSTICK_EPSIZE 8
|
||||||
#define DIGITIZER_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);
|
||||||
|
|
|
@ -44,6 +44,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
# include "ring_buffer.h"
|
# include "ring_buffer.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef OS_DETECTION_ENABLE
|
||||||
|
# include "os_detection.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#define NEXT_INTERFACE __COUNTER__
|
#define NEXT_INTERFACE __COUNTER__
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1013,6 +1017,9 @@ USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) {
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
#ifdef OS_DETECTION_ENABLE
|
||||||
|
process_wlength(rq->wLength.word);
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
case USBDESCR_HID:
|
case USBDESCR_HID:
|
||||||
switch (rq->wValue.bytes[0]) {
|
switch (rq->wValue.bytes[0]) {
|
||||||
|
|
Loading…
Reference in New Issue