summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/feature_os_detection.md50
-rw-r--r--quantum/keyboard.c7
-rw-r--r--quantum/os_detection.c170
-rw-r--r--quantum/os_detection.h22
-rw-r--r--quantum/os_detection/tests/os_detection.cpp229
-rw-r--r--quantum/os_detection/tests/rules.mk4
-rw-r--r--quantum/quantum.h4
-rw-r--r--tmk_core/protocol/usb_device_state.c9
8 files changed, 437 insertions, 58 deletions
diff --git a/docs/feature_os_detection.md b/docs/feature_os_detection.md
index 907638bcfa..a50ee7ccc2 100644
--- a/docs/feature_os_detection.md
+++ b/docs/feature_os_detection.md
@@ -14,7 +14,7 @@ In your `rules.mk` add:
14OS_DETECTION_ENABLE = yes 14OS_DETECTION_ENABLE = yes
15``` 15```
16 16
17Include `"os_detection.h"` in your `keymap.c`. 17It will automatically include the required headers file.
18It declares `os_variant_t detected_host_os(void);` which you can call to get detected OS. 18It declares `os_variant_t detected_host_os(void);` which you can call to get detected OS.
19 19
20It returns one of the following values: 20It returns one of the following values:
@@ -32,6 +32,54 @@ enum {
32?> Note that it takes some time after firmware is booted to detect the OS. 32?> Note that it takes some time after firmware is booted to detect the OS.
33This 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. 33This 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.
34 34
35## Callbacks :id=callbacks
36
37If 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`.
38
39```c
40bool process_detected_host_os_kb(os_variant_t detected_os) {
41 if (!process_detected_host_os_user(detected_os)) {
42 return false;
43 }
44 switch (detected_os) {
45 case OS_MACOS:
46 case OS_IOS:
47 rgb_matrix_set_color_all(RGB_WHITE);
48 break;
49 case OS_WINDOWS:
50 rgb_matrix_set_color_all(RGB_BLUE);
51 break;
52 case OS_LINUX:
53 rgb_matrix_set_color_all(RGB_ORANGE);
54 break;
55 case OS_UNSURE:
56 rgb_matrix_set_color_all(RGB_RED);
57 break;
58 }
59
60 return true;
61}
62```
63
64## OS detection stability
65
66The OS detection is currently handled while the USB device descriptor is being assembled.
67The process is done in steps, generating a number of intermediate results until it stabilizes.
68We therefore resort to debouncing the result until it has been stable for a given amount of milliseconds.
69This amount can be configured, in case your board is not stable within the default debouncing time of 200ms.
70
71## KVM and USB switches
72
73Some KVM and USB switches may not trigger the USB controller on the keyboard to fully reset upon switching machines.
74If 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.
75
76## Configuration Options
77
78* `#define OS_DETECTION_DEBOUNCE 200`
79 * defined the debounce time for OS detection, in milliseconds
80* `#define OS_DETECTION_KEYBOARD_RESET`
81 * enables the keyboard reset upon a USB device reinitilization, such as switching devices on some KVMs
82
35## Debug 83## Debug
36 84
37If OS is guessed incorrectly, you may want to collect data about USB setup packets to refine the detection logic. 85If OS is guessed incorrectly, you may want to collect data about USB setup packets to refine the detection logic.
diff --git a/quantum/keyboard.c b/quantum/keyboard.c
index ab25b02547..1d6657c230 100644
--- a/quantum/keyboard.c
+++ b/quantum/keyboard.c
@@ -137,6 +137,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
137#ifdef WPM_ENABLE 137#ifdef WPM_ENABLE
138# include "wpm.h" 138# include "wpm.h"
139#endif 139#endif
140#ifdef OS_DETECTION_ENABLE
141# include "os_detection.h"
142#endif
140 143
141static uint32_t last_input_modification_time = 0; 144static uint32_t last_input_modification_time = 0;
142uint32_t last_input_activity_time(void) { 145uint32_t last_input_activity_time(void) {
@@ -741,4 +744,8 @@ void keyboard_task(void) {
741#endif 744#endif
742 745
743 led_task(); 746 led_task();
747
748#ifdef OS_DETECTION_ENABLE
749 os_detection_task();
750#endif
744} 751}
diff --git a/quantum/os_detection.c b/quantum/os_detection.c
index e606227136..b674f05b35 100644
--- a/quantum/os_detection.c
+++ b/quantum/os_detection.c
@@ -16,20 +16,25 @@
16 16
17#include "os_detection.h" 17#include "os_detection.h"
18 18
19#include <string.h> 19#ifdef OS_DETECTION_ENABLE
20 20
21#ifdef OS_DETECTION_DEBUG_ENABLE 21# include <string.h>
22# include "eeconfig.h" 22# include "timer.h"
23# include "eeprom.h" 23# ifdef OS_DETECTION_KEYBOARD_RESET
24# include "print.h" 24# include "quantum.h"
25# endif
25 26
26# define STORED_USB_SETUPS 50 27# ifdef OS_DETECTION_DEBUG_ENABLE
27# define EEPROM_USER_OFFSET (uint8_t*)EECONFIG_SIZE 28# include "eeconfig.h"
29# include "eeprom.h"
30# include "print.h"
28 31
29uint16_t usb_setups[STORED_USB_SETUPS]; 32# define STORED_USB_SETUPS 50
30#endif 33# define EEPROM_USER_OFFSET (uint8_t*)EECONFIG_SIZE
34
35static uint16_t usb_setups[STORED_USB_SETUPS];
36# endif
31 37
32#ifdef OS_DETECTION_ENABLE
33struct setups_data_t { 38struct setups_data_t {
34 uint8_t count; 39 uint8_t count;
35 uint8_t cnt_02; 40 uint8_t cnt_02;
@@ -45,43 +50,63 @@ struct setups_data_t setups_data = {
45 .cnt_ff = 0, 50 .cnt_ff = 0,
46}; 51};
47 52
48os_variant_t detected_os = OS_UNSURE; 53# ifndef OS_DETECTION_DEBOUNCE
54# define OS_DETECTION_DEBOUNCE 200
55# endif
49 56
50// Some collected sequences of wLength can be found in tests. 57// 2s should always be more than enough (otherwise, you may have other issues)
51void make_guess(void) { 58# if OS_DETECTION_DEBOUNCE > 2000
52 if (setups_data.count < 3) { 59# undef OS_DETECTION_DEBOUNCE
53 return; 60# define OS_DETECTION_DEBOUNCE 2000
54 } 61# endif
55 if (setups_data.cnt_ff >= 2 && setups_data.cnt_04 >= 1) { 62
56 detected_os = OS_WINDOWS; 63typedef uint16_t debouncing_t;
57 return; 64
58 } 65static volatile os_variant_t detected_os = OS_UNSURE;
59 if (setups_data.count == setups_data.cnt_ff) { 66static os_variant_t reported_os = OS_UNSURE;
60 // Linux has 3 packets with 0xFF. 67
61 detected_os = OS_LINUX; 68// we need to be able to report OS_UNSURE if that is the stable result of the guesses
62 return; 69static bool first_report = true;
63 } 70
64 if (setups_data.count == 5 && setups_data.last_wlength == 0xFF && setups_data.cnt_ff == 1 && setups_data.cnt_02 == 2) { 71// to react on USB state changes
65 detected_os = OS_MACOS; 72static volatile enum usb_device_state current_usb_device_state = USB_DEVICE_STATE_INIT;
66 return; 73static enum usb_device_state reported_usb_device_state = USB_DEVICE_STATE_INIT;
67 } 74
68 if (setups_data.count == 4 && setups_data.cnt_ff == 0 && setups_data.cnt_02 == 2) { 75// the OS detection might be unstable for a while, "debounce" it
69 // iOS and iPadOS don't have the last 0xFF packet. 76static volatile bool debouncing = false;
70 detected_os = OS_IOS; 77static volatile fast_timer_t last_time;
71 return; 78
72 } 79void os_detection_task(void) {
73 if (setups_data.cnt_ff == 0 && setups_data.cnt_02 == 3 && setups_data.cnt_04 == 1) { 80 if (current_usb_device_state == USB_DEVICE_STATE_CONFIGURED) {
74 // This is actually PS5. 81 // debouncing goes for both the detected OS as well as the USB state
75 detected_os = OS_LINUX; 82 if (debouncing && timer_elapsed_fast(last_time) >= OS_DETECTION_DEBOUNCE) {
76 return; 83 debouncing = false;
84 reported_usb_device_state = current_usb_device_state;
85 if (detected_os != reported_os || first_report) {
86 first_report = false;
87 reported_os = detected_os;
88 process_detected_host_os_kb(detected_os);
89 }
90 }
77 } 91 }
78 if (setups_data.cnt_ff >= 1 && setups_data.cnt_02 == 0 && setups_data.cnt_04 == 0) { 92# ifdef OS_DETECTION_KEYBOARD_RESET
79 // This is actually Quest 2 or Nintendo Switch. 93 // resetting the keyboard on the USB device state change callback results in instability, so delegate that to this task
80 detected_os = OS_LINUX; 94 // only take action if it's been stable at least once, to avoid issues with some KVMs
81 return; 95 else if (current_usb_device_state == USB_DEVICE_STATE_INIT && reported_usb_device_state != USB_DEVICE_STATE_INIT) {
96 soft_reset_keyboard();
82 } 97 }
98# endif
83} 99}
84 100
101__attribute__((weak)) bool process_detected_host_os_kb(os_variant_t detected_os) {
102 return process_detected_host_os_user(detected_os);
103}
104
105__attribute__((weak)) bool process_detected_host_os_user(os_variant_t detected_os) {
106 return true;
107}
108
109// Some collected sequences of wLength can be found in tests.
85void process_wlength(const uint16_t w_length) { 110void process_wlength(const uint16_t w_length) {
86# ifdef OS_DETECTION_DEBUG_ENABLE 111# ifdef OS_DETECTION_DEBUG_ENABLE
87 usb_setups[setups_data.count] = w_length; 112 usb_setups[setups_data.count] = w_length;
@@ -95,7 +120,37 @@ void process_wlength(const uint16_t w_length) {
95 } else if (w_length == 0xFF) { 120 } else if (w_length == 0xFF) {
96 setups_data.cnt_ff++; 121 setups_data.cnt_ff++;
97 } 122 }
98 make_guess(); 123
124 // now try to make a guess
125 os_variant_t guessed = OS_UNSURE;
126 if (setups_data.count >= 3) {
127 if (setups_data.cnt_ff >= 2 && setups_data.cnt_04 >= 1) {
128 guessed = OS_WINDOWS;
129 } else if (setups_data.count == setups_data.cnt_ff) {
130 // Linux has 3 packets with 0xFF.
131 guessed = OS_LINUX;
132 } else if (setups_data.count == 5 && setups_data.last_wlength == 0xFF && setups_data.cnt_ff == 1 && setups_data.cnt_02 == 2) {
133 guessed = OS_MACOS;
134 } else if (setups_data.count == 4 && setups_data.cnt_ff == 0 && setups_data.cnt_02 == 2) {
135 // iOS and iPadOS don't have the last 0xFF packet.
136 guessed = OS_IOS;
137 } else if (setups_data.cnt_ff == 0 && setups_data.cnt_02 == 3 && setups_data.cnt_04 == 1) {
138 // This is actually PS5.
139 guessed = OS_LINUX;
140 } else if (setups_data.cnt_ff >= 1 && setups_data.cnt_02 == 0 && setups_data.cnt_04 == 0) {
141 // This is actually Quest 2 or Nintendo Switch.
142 guessed = OS_LINUX;
143 }
144 }
145
146 // only replace the guessed value if not unsure
147 if (guessed != OS_UNSURE) {
148 detected_os = guessed;
149 }
150
151 // whatever the result, debounce
152 last_time = timer_read_fast();
153 debouncing = true;
99} 154}
100 155
101os_variant_t detected_host_os(void) { 156os_variant_t detected_host_os(void) {
@@ -104,25 +159,38 @@ os_variant_t detected_host_os(void) {
104 159
105void erase_wlength_data(void) { 160void erase_wlength_data(void) {
106 memset(&setups_data, 0, sizeof(setups_data)); 161 memset(&setups_data, 0, sizeof(setups_data));
107 detected_os = OS_UNSURE; 162 detected_os = OS_UNSURE;
163 reported_os = OS_UNSURE;
164 current_usb_device_state = USB_DEVICE_STATE_INIT;
165 reported_usb_device_state = USB_DEVICE_STATE_INIT;
166 debouncing = false;
167 first_report = true;
168}
169
170void os_detection_notify_usb_device_state_change(enum usb_device_state usb_device_state) {
171 // treat this like any other source of instability
172 current_usb_device_state = usb_device_state;
173 last_time = timer_read_fast();
174 debouncing = true;
108} 175}
109 176
110# if defined(SPLIT_KEYBOARD) && defined(SPLIT_DETECTED_OS_ENABLE) 177# if defined(SPLIT_KEYBOARD) && defined(SPLIT_DETECTED_OS_ENABLE)
111void slave_update_detected_host_os(os_variant_t os) { 178void slave_update_detected_host_os(os_variant_t os) {
112 detected_os = os; 179 detected_os = os;
180 last_time = timer_read_fast();
181 debouncing = true;
113} 182}
114# endif // defined(SPLIT_KEYBOARD) && defined(SPLIT_DETECTED_OS_ENABLE) 183# endif
115#endif // OS_DETECTION_ENABLE
116 184
117#ifdef OS_DETECTION_DEBUG_ENABLE 185# ifdef OS_DETECTION_DEBUG_ENABLE
118void print_stored_setups(void) { 186void print_stored_setups(void) {
119# ifdef CONSOLE_ENABLE 187# ifdef CONSOLE_ENABLE
120 uint8_t cnt = eeprom_read_byte(EEPROM_USER_OFFSET); 188 uint8_t cnt = eeprom_read_byte(EEPROM_USER_OFFSET);
121 for (uint16_t i = 0; i < cnt; ++i) { 189 for (uint16_t i = 0; i < cnt; ++i) {
122 uint16_t* addr = (uint16_t*)EEPROM_USER_OFFSET + i * sizeof(uint16_t) + sizeof(uint8_t); 190 uint16_t* addr = (uint16_t*)EEPROM_USER_OFFSET + i * sizeof(uint16_t) + sizeof(uint8_t);
123 xprintf("i: %d, wLength: 0x%02X\n", i, eeprom_read_word(addr)); 191 xprintf("i: %d, wLength: 0x%02X\n", i, eeprom_read_word(addr));
124 } 192 }
125# endif 193# endif
126} 194}
127 195
128void store_setups_in_eeprom(void) { 196void store_setups_in_eeprom(void) {
@@ -133,4 +201,6 @@ void store_setups_in_eeprom(void) {
133 } 201 }
134} 202}
135 203
136#endif // OS_DETECTION_DEBUG_ENABLE 204# endif // OS_DETECTION_DEBUG_ENABLE
205
206#endif
diff --git a/quantum/os_detection.h b/quantum/os_detection.h
index 3496ea0ed2..470f30943a 100644
--- a/quantum/os_detection.h
+++ b/quantum/os_detection.h
@@ -16,9 +16,12 @@
16 16
17#pragma once 17#pragma once
18 18
19#include <stdint.h>
20
21#ifdef OS_DETECTION_ENABLE 19#ifdef OS_DETECTION_ENABLE
20
21# include <stdint.h>
22# include <stdbool.h>
23# include "usb_device_state.h"
24
22typedef enum { 25typedef enum {
23 OS_UNSURE, 26 OS_UNSURE,
24 OS_LINUX, 27 OS_LINUX,
@@ -30,13 +33,20 @@ typedef enum {
30void process_wlength(const uint16_t w_length); 33void process_wlength(const uint16_t w_length);
31os_variant_t detected_host_os(void); 34os_variant_t detected_host_os(void);
32void erase_wlength_data(void); 35void erase_wlength_data(void);
36void os_detection_notify_usb_device_state_change(enum usb_device_state usb_device_state);
37
38void os_detection_task(void);
39
40bool process_detected_host_os_kb(os_variant_t os);
41bool process_detected_host_os_user(os_variant_t os);
33 42
34# if defined(SPLIT_KEYBOARD) && defined(SPLIT_DETECTED_OS_ENABLE) 43# if defined(SPLIT_KEYBOARD) && defined(SPLIT_DETECTED_OS_ENABLE)
35void slave_update_detected_host_os(os_variant_t os); 44void slave_update_detected_host_os(os_variant_t os);
36# endif // defined(SPLIT_KEYBOARD) && defined(SPLIT_DETECTED_OS_ENABLE) 45# endif
37#endif
38 46
39#ifdef OS_DETECTION_DEBUG_ENABLE 47# ifdef OS_DETECTION_DEBUG_ENABLE
40void print_stored_setups(void); 48void print_stored_setups(void);
41void store_setups_in_eeprom(void); 49void store_setups_in_eeprom(void);
42#endif 50# endif
51
52#endif // OS_DETECTION_ENABLE
diff --git a/quantum/os_detection/tests/os_detection.cpp b/quantum/os_detection/tests/os_detection.cpp
index 102349852e..11e93fdea8 100644
--- a/quantum/os_detection/tests/os_detection.cpp
+++ b/quantum/os_detection/tests/os_detection.cpp
@@ -18,12 +18,20 @@
18 18
19extern "C" { 19extern "C" {
20#include "os_detection.h" 20#include "os_detection.h"
21#include "timer.h"
22
23void advance_time(uint32_t ms);
21} 24}
22 25
26static uint32_t reported_count;
27static os_variant_t reported_os;
28
23class OsDetectionTest : public ::testing::Test { 29class OsDetectionTest : public ::testing::Test {
24 protected: 30 protected:
25 void SetUp() override { 31 void SetUp() override {
26 erase_wlength_data(); 32 erase_wlength_data();
33 reported_count = 0;
34 reported_os = OS_UNSURE;
27 } 35 }
28}; 36};
29 37
@@ -34,6 +42,24 @@ os_variant_t check_sequence(const std::vector<uint16_t> &w_lengths) {
34 return detected_host_os(); 42 return detected_host_os();
35} 43}
36 44
45bool process_detected_host_os_kb(os_variant_t os) {
46 reported_count = reported_count + 1;
47 reported_os = os;
48}
49
50void assert_not_reported(void) {
51 // check that it does not report the result, nor any intermediate results
52 EXPECT_EQ(reported_count, 0);
53 EXPECT_EQ(reported_os, OS_UNSURE);
54}
55
56void assert_reported(os_variant_t os) {
57 // check that it reports exclusively the result, not any intermediate results
58 EXPECT_EQ(reported_count, 1);
59 EXPECT_EQ(reported_os, os);
60 EXPECT_EQ(reported_os, detected_host_os());
61}
62
37/* Some collected data. 63/* Some collected data.
38 64
39ChibiOS: 65ChibiOS:
@@ -77,88 +103,291 @@ Quest 2: [FF, FF, FF, FE, ...]
77*/ 103*/
78TEST_F(OsDetectionTest, TestLinux) { 104TEST_F(OsDetectionTest, TestLinux) {
79 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0xFF}), OS_LINUX); 105 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0xFF}), OS_LINUX);
106 os_detection_task();
107 assert_not_reported();
80} 108}
81 109
82TEST_F(OsDetectionTest, TestChibiosMacos) { 110TEST_F(OsDetectionTest, TestChibiosMacos) {
83 EXPECT_EQ(check_sequence({0x2, 0x24, 0x2, 0x28, 0xFF}), OS_MACOS); 111 EXPECT_EQ(check_sequence({0x2, 0x24, 0x2, 0x28, 0xFF}), OS_MACOS);
112 os_detection_task();
113 assert_not_reported();
84} 114}
85 115
86TEST_F(OsDetectionTest, TestLufaMacos) { 116TEST_F(OsDetectionTest, TestLufaMacos) {
87 EXPECT_EQ(check_sequence({0x2, 0x10, 0x2, 0xE, 0xFF}), OS_MACOS); 117 EXPECT_EQ(check_sequence({0x2, 0x10, 0x2, 0xE, 0xFF}), OS_MACOS);
118 os_detection_task();
119 assert_not_reported();
88} 120}
89 121
90TEST_F(OsDetectionTest, TestVusbMacos) { 122TEST_F(OsDetectionTest, TestVusbMacos) {
91 EXPECT_EQ(check_sequence({0x2, 0xE, 0x2, 0xE, 0xFF}), OS_MACOS); 123 EXPECT_EQ(check_sequence({0x2, 0xE, 0x2, 0xE, 0xFF}), OS_MACOS);
124 os_detection_task();
125 assert_not_reported();
92} 126}
93 127
94TEST_F(OsDetectionTest, TestChibiosIos) { 128TEST_F(OsDetectionTest, TestChibiosIos) {
95 EXPECT_EQ(check_sequence({0x2, 0x24, 0x2, 0x28}), OS_IOS); 129 EXPECT_EQ(check_sequence({0x2, 0x24, 0x2, 0x28}), OS_IOS);
130 os_detection_task();
131 assert_not_reported();
96} 132}
97 133
98TEST_F(OsDetectionTest, TestLufaIos) { 134TEST_F(OsDetectionTest, TestLufaIos) {
99 EXPECT_EQ(check_sequence({0x2, 0x10, 0x2, 0xE}), OS_IOS); 135 EXPECT_EQ(check_sequence({0x2, 0x10, 0x2, 0xE}), OS_IOS);
136 os_detection_task();
137 assert_not_reported();
100} 138}
101 139
102TEST_F(OsDetectionTest, TestVusbIos) { 140TEST_F(OsDetectionTest, TestVusbIos) {
103 EXPECT_EQ(check_sequence({0x2, 0xE, 0x2, 0xE}), OS_IOS); 141 EXPECT_EQ(check_sequence({0x2, 0xE, 0x2, 0xE}), OS_IOS);
142 os_detection_task();
143 assert_not_reported();
104} 144}
105 145
106TEST_F(OsDetectionTest, TestChibiosWindows10) { 146TEST_F(OsDetectionTest, TestChibiosWindows10) {
107 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); 147 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);
148 os_detection_task();
149 assert_not_reported();
108} 150}
109 151
110TEST_F(OsDetectionTest, TestChibiosWindows10_2) { 152TEST_F(OsDetectionTest, TestChibiosWindows10_2) {
111 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0x24, 0x4, 0x24, 0x4, 0x24, 0x4, 0x24, 0x4, 0x24}), OS_WINDOWS); 153 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0x24, 0x4, 0x24, 0x4, 0x24, 0x4, 0x24, 0x4, 0x24}), OS_WINDOWS);
154 os_detection_task();
155 assert_not_reported();
112} 156}
113 157
114TEST_F(OsDetectionTest, TestLufaWindows10) { 158TEST_F(OsDetectionTest, TestLufaWindows10) {
115 EXPECT_EQ(check_sequence({0x12, 0xFF, 0xFF, 0x4, 0x10, 0xFF, 0xFF, 0xFF, 0x4, 0x10, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A}), OS_WINDOWS); 159 EXPECT_EQ(check_sequence({0x12, 0xFF, 0xFF, 0x4, 0x10, 0xFF, 0xFF, 0xFF, 0x4, 0x10, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A}), OS_WINDOWS);
160 os_detection_task();
161 assert_not_reported();
116} 162}
117 163
118TEST_F(OsDetectionTest, TestLufaWindows10_2) { 164TEST_F(OsDetectionTest, TestLufaWindows10_2) {
119 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0x10, 0xFF, 0x4, 0xFF, 0x10, 0xFF, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A}), OS_WINDOWS); 165 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0x10, 0xFF, 0x4, 0xFF, 0x10, 0xFF, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A}), OS_WINDOWS);
166 os_detection_task();
167 assert_not_reported();
120} 168}
121 169
122TEST_F(OsDetectionTest, TestLufaWindows10_3) { 170TEST_F(OsDetectionTest, TestLufaWindows10_3) {
123 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0x10, 0x4, 0x10}), OS_WINDOWS); 171 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0x10, 0x4, 0x10}), OS_WINDOWS);
172 os_detection_task();
173 assert_not_reported();
124} 174}
125 175
126TEST_F(OsDetectionTest, TestVusbWindows10) { 176TEST_F(OsDetectionTest, TestVusbWindows10) {
127 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0xE, 0xFF}), OS_WINDOWS); 177 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0xE, 0xFF}), OS_WINDOWS);
178 os_detection_task();
179 assert_not_reported();
128} 180}
129 181
130TEST_F(OsDetectionTest, TestVusbWindows10_2) { 182TEST_F(OsDetectionTest, TestVusbWindows10_2) {
131 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0xE, 0x4}), OS_WINDOWS); 183 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0xE, 0x4}), OS_WINDOWS);
184 os_detection_task();
185 assert_not_reported();
132} 186}
133 187
134TEST_F(OsDetectionTest, TestChibiosPs5) { 188TEST_F(OsDetectionTest, TestChibiosPs5) {
135 EXPECT_EQ(check_sequence({0x2, 0x4, 0x2, 0x28, 0x2, 0x24}), OS_LINUX); 189 EXPECT_EQ(check_sequence({0x2, 0x4, 0x2, 0x28, 0x2, 0x24}), OS_LINUX);
190 os_detection_task();
191 assert_not_reported();
136} 192}
137 193
138TEST_F(OsDetectionTest, TestLufaPs5) { 194TEST_F(OsDetectionTest, TestLufaPs5) {
139 EXPECT_EQ(check_sequence({0x2, 0x4, 0x2, 0xE, 0x2, 0x10}), OS_LINUX); 195 EXPECT_EQ(check_sequence({0x2, 0x4, 0x2, 0xE, 0x2, 0x10}), OS_LINUX);
196 os_detection_task();
197 assert_not_reported();
140} 198}
141 199
142TEST_F(OsDetectionTest, TestVusbPs5) { 200TEST_F(OsDetectionTest, TestVusbPs5) {
143 EXPECT_EQ(check_sequence({0x2, 0x4, 0x2, 0xE, 0x2}), OS_LINUX); 201 EXPECT_EQ(check_sequence({0x2, 0x4, 0x2, 0xE, 0x2}), OS_LINUX);
202 os_detection_task();
203 assert_not_reported();
144} 204}
145 205
146TEST_F(OsDetectionTest, TestChibiosNintendoSwitch) { 206TEST_F(OsDetectionTest, TestChibiosNintendoSwitch) {
147 EXPECT_EQ(check_sequence({0x82, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40}), OS_LINUX); 207 EXPECT_EQ(check_sequence({0x82, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40}), OS_LINUX);
208 os_detection_task();
209 assert_not_reported();
148} 210}
149 211
150TEST_F(OsDetectionTest, TestLufaNintendoSwitch) { 212TEST_F(OsDetectionTest, TestLufaNintendoSwitch) {
151 EXPECT_EQ(check_sequence({0x82, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40}), OS_LINUX); 213 EXPECT_EQ(check_sequence({0x82, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40}), OS_LINUX);
214 os_detection_task();
215 assert_not_reported();
152} 216}
153 217
154TEST_F(OsDetectionTest, TestVusbNintendoSwitch) { 218TEST_F(OsDetectionTest, TestVusbNintendoSwitch) {
155 EXPECT_EQ(check_sequence({0x82, 0xFF, 0x40, 0x40}), OS_LINUX); 219 EXPECT_EQ(check_sequence({0x82, 0xFF, 0x40, 0x40}), OS_LINUX);
220 os_detection_task();
221 assert_not_reported();
156} 222}
157 223
158TEST_F(OsDetectionTest, TestChibiosQuest2) { 224TEST_F(OsDetectionTest, TestChibiosQuest2) {
159 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF}), OS_LINUX); 225 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF}), OS_LINUX);
226 os_detection_task();
227 assert_not_reported();
160} 228}
161 229
162TEST_F(OsDetectionTest, TestVusbQuest2) { 230TEST_F(OsDetectionTest, TestVusbQuest2) {
163 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0xFF, 0xFE}), OS_LINUX); 231 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0xFF, 0xFE}), OS_LINUX);
232 os_detection_task();
233 assert_not_reported();
234}
235
236// Regression reported in https://github.com/qmk/qmk_firmware/pull/21777#issuecomment-1922815841
237TEST_F(OsDetectionTest, TestDetectMacM1AsIOS) {
238 EXPECT_EQ(check_sequence({0x02, 0x32, 0x02, 0x24, 0x101, 0xFF}), OS_IOS);
239 os_detection_task();
240 assert_not_reported();
241}
242
243TEST_F(OsDetectionTest, TestDoNotReportIfUsbUnstable) {
244 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0xFF, 0xFE}), OS_LINUX);
245 os_detection_task();
246 assert_not_reported();
247
248 advance_time(OS_DETECTION_DEBOUNCE);
249 os_detection_task();
250 assert_not_reported();
251 EXPECT_EQ(detected_host_os(), OS_LINUX);
252}
253
254TEST_F(OsDetectionTest, TestReportAfterDebounce) {
255 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0xFF, 0xFE}), OS_LINUX);
256 os_detection_notify_usb_device_state_change(USB_DEVICE_STATE_CONFIGURED);
257 os_detection_task();
258 assert_not_reported();
259
260 advance_time(1);
261 os_detection_task();
262 assert_not_reported();
263 EXPECT_EQ(detected_host_os(), OS_LINUX);
264
265 advance_time(OS_DETECTION_DEBOUNCE - 3);
266 os_detection_task();
267 assert_not_reported();
268 EXPECT_EQ(detected_host_os(), OS_LINUX);
269
270 advance_time(1);
271 os_detection_task();
272 assert_not_reported();
273 EXPECT_EQ(detected_host_os(), OS_LINUX);
274
275 // advancing the timer alone must not cause a report
276 advance_time(1);
277 assert_not_reported();
278 EXPECT_EQ(detected_host_os(), OS_LINUX);
279 // the task will cause a report
280 os_detection_task();
281 assert_reported(OS_LINUX);
282 EXPECT_EQ(detected_host_os(), OS_LINUX);
283
284 // check that it remains the same after a long time
285 advance_time(OS_DETECTION_DEBOUNCE * 15);
286 assert_reported(OS_LINUX);
287 EXPECT_EQ(detected_host_os(), OS_LINUX);
288}
289
290TEST_F(OsDetectionTest, TestReportAfterDebounceLongWait) {
291 EXPECT_EQ(check_sequence({0x12, 0xFF, 0xFF, 0x4, 0x10, 0xFF, 0xFF, 0xFF, 0x4, 0x10, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A}), OS_WINDOWS);
292 os_detection_notify_usb_device_state_change(USB_DEVICE_STATE_CONFIGURED);
293 os_detection_task();
294 assert_not_reported();
295
296 advance_time(1);
297 os_detection_task();
298 assert_not_reported();
299 EXPECT_EQ(detected_host_os(), OS_WINDOWS);
300
301 // advancing the timer alone must not cause a report
302 advance_time(OS_DETECTION_DEBOUNCE * 15);
303 assert_not_reported();
304 EXPECT_EQ(detected_host_os(), OS_WINDOWS);
305 // the task will cause a report
306 os_detection_task();
307 assert_reported(OS_WINDOWS);
308 EXPECT_EQ(detected_host_os(), OS_WINDOWS);
309
310 // check that it remains the same after a long time
311 advance_time(OS_DETECTION_DEBOUNCE * 10);
312 os_detection_task();
313 assert_reported(OS_WINDOWS);
314 EXPECT_EQ(detected_host_os(), OS_WINDOWS);
315}
316
317TEST_F(OsDetectionTest, TestReportUnsure) {
318 EXPECT_EQ(check_sequence({0x12, 0xFF}), OS_UNSURE);
319 os_detection_notify_usb_device_state_change(USB_DEVICE_STATE_CONFIGURED);
320 os_detection_task();
321 assert_not_reported();
322
323 advance_time(1);
324 os_detection_task();
325 assert_not_reported();
326 EXPECT_EQ(detected_host_os(), OS_UNSURE);
327
328 // advancing the timer alone must not cause a report
329 advance_time(OS_DETECTION_DEBOUNCE - 1);
330 assert_not_reported();
331 EXPECT_EQ(detected_host_os(), OS_UNSURE);
332 // the task will cause a report
333 os_detection_task();
334 assert_reported(OS_UNSURE);
335 EXPECT_EQ(detected_host_os(), OS_UNSURE);
336
337 // check that it remains the same after a long time
338 advance_time(OS_DETECTION_DEBOUNCE * 10);
339 os_detection_task();
340 assert_reported(OS_UNSURE);
341 EXPECT_EQ(detected_host_os(), OS_UNSURE);
342}
343
344TEST_F(OsDetectionTest, TestDoNotReportIntermediateResults) {
345 EXPECT_EQ(check_sequence({0x12, 0xFF}), OS_UNSURE);
346 os_detection_notify_usb_device_state_change(USB_DEVICE_STATE_CONFIGURED);
347 os_detection_task();
348 assert_not_reported();
349
350 advance_time(OS_DETECTION_DEBOUNCE - 1);
351 os_detection_task();
352 assert_not_reported();
353 EXPECT_EQ(detected_host_os(), OS_UNSURE);
354
355 // at this stage, the final result has not been reached yet
356 EXPECT_EQ(check_sequence({0xFF}), OS_LINUX);
357 os_detection_notify_usb_device_state_change(USB_DEVICE_STATE_CONFIGURED);
358 advance_time(OS_DETECTION_DEBOUNCE - 1);
359 os_detection_task();
360 assert_not_reported();
361 // the intermedite but yet unstable result is exposed through detected_host_os()
362 EXPECT_EQ(detected_host_os(), OS_LINUX);
363
364 // the remainder is processed
365 EXPECT_EQ(check_sequence({0x4, 0x10, 0xFF, 0xFF, 0xFF, 0x4, 0x10, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A}), OS_WINDOWS);
366 os_detection_notify_usb_device_state_change(USB_DEVICE_STATE_CONFIGURED);
367 advance_time(OS_DETECTION_DEBOUNCE - 1);
368 os_detection_task();
369 assert_not_reported();
370 EXPECT_EQ(detected_host_os(), OS_WINDOWS);
371
372 // advancing the timer alone must not cause a report
373 advance_time(1);
374 assert_not_reported();
375 EXPECT_EQ(detected_host_os(), OS_WINDOWS);
376 // the task will cause a report
377 os_detection_task();
378 assert_reported(OS_WINDOWS);
379 EXPECT_EQ(detected_host_os(), OS_WINDOWS);
380
381 // check that it remains the same after a long time
382 advance_time(OS_DETECTION_DEBOUNCE * 10);
383 os_detection_task();
384 assert_reported(OS_WINDOWS);
385 EXPECT_EQ(detected_host_os(), OS_WINDOWS);
386}
387
388TEST_F(OsDetectionTest, TestDoNotGoBackToUnsure) {
389 // 0x02 would cause it to go back to Unsure, so check that it does not
390 EXPECT_EQ(check_sequence({0xFF, 0xFF, 0xFF, 0xFE, 0x02}), OS_LINUX);
391 os_detection_task();
392 assert_not_reported();
164} 393}
diff --git a/quantum/os_detection/tests/rules.mk b/quantum/os_detection/tests/rules.mk
index 9bfe373f46..1b69b71ba9 100644
--- a/quantum/os_detection/tests/rules.mk
+++ b/quantum/os_detection/tests/rules.mk
@@ -1,5 +1,7 @@
1os_detection_DEFS := -DOS_DETECTION_ENABLE 1os_detection_DEFS := -DOS_DETECTION_ENABLE
2os_detection_DEFS += -DOS_DETECTION_DEBOUNCE=50
2 3
3os_detection_SRC := \ 4os_detection_SRC := \
4 $(QUANTUM_PATH)/os_detection/tests/os_detection.cpp \ 5 $(QUANTUM_PATH)/os_detection/tests/os_detection.cpp \
5 $(QUANTUM_PATH)/os_detection.c 6 $(QUANTUM_PATH)/os_detection.c \
7 $(PLATFORM_PATH)/$(PLATFORM_KEY)/timer.c
diff --git a/quantum/quantum.h b/quantum/quantum.h
index 98d848581d..5446ab1ad7 100644
--- a/quantum/quantum.h
+++ b/quantum/quantum.h
@@ -236,6 +236,10 @@ extern layer_state_t layer_state;
236# include "process_repeat_key.h" 236# include "process_repeat_key.h"
237#endif 237#endif
238 238
239#ifdef OS_DETECTION_ENABLE
240# include "os_detection.h"
241#endif
242
239void set_single_persistent_default_layer(uint8_t default_layer); 243void set_single_persistent_default_layer(uint8_t default_layer);
240 244
241#define IS_LAYER_ON(layer) layer_state_is(layer) 245#define IS_LAYER_ON(layer) layer_state_is(layer)
diff --git a/tmk_core/protocol/usb_device_state.c b/tmk_core/protocol/usb_device_state.c
index 8d56ba4d2f..4cd241528d 100644
--- a/tmk_core/protocol/usb_device_state.c
+++ b/tmk_core/protocol/usb_device_state.c
@@ -20,6 +20,10 @@
20# include "haptic.h" 20# include "haptic.h"
21#endif 21#endif
22 22
23#ifdef OS_DETECTION_ENABLE
24# include "os_detection.h"
25#endif
26
23enum usb_device_state usb_device_state = USB_DEVICE_STATE_NO_INIT; 27enum usb_device_state usb_device_state = USB_DEVICE_STATE_NO_INIT;
24 28
25__attribute__((weak)) void notify_usb_device_state_change_kb(enum usb_device_state usb_device_state) { 29__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
32#if defined(HAPTIC_ENABLE) && HAPTIC_OFF_IN_LOW_POWER 36#if defined(HAPTIC_ENABLE) && HAPTIC_OFF_IN_LOW_POWER
33 haptic_notify_usb_device_state_change(); 37 haptic_notify_usb_device_state_change();
34#endif 38#endif
39
35 notify_usb_device_state_change_kb(usb_device_state); 40 notify_usb_device_state_change_kb(usb_device_state);
41
42#ifdef OS_DETECTION_ENABLE
43 os_detection_notify_usb_device_state_change(usb_device_state);
44#endif
36} 45}
37 46
38void usb_device_state_set_configuration(bool isConfigured, uint8_t configurationNumber) { 47void usb_device_state_set_configuration(bool isConfigured, uint8_t configurationNumber) {