forked from qmk/qmk_firmware
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Detect host OS based on USB fingerprint (qmk#18463)
Co-authored-by: Drashna Jaelre <drashna@live.com> Co-authored-by: Nick Brassel <nick@tzarc.org>
- Loading branch information
Showing
15 changed files
with
448 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Oops, something went wrong.