summaryrefslogtreecommitdiff
path: root/quantum
diff options
context:
space:
mode:
authorAndre Brait <andrebrait@gmail.com>2024-02-16 15:19:02 +0100
committerGitHub <noreply@github.com>2024-02-17 01:19:02 +1100
commit80f3da36e571fa702b1d3df693fd545801250eca (patch)
tree8fe09c9e53bbe8534372a87bab3d4edfd61e5fa8 /quantum
parent77e88674986ee14bd1799b1ab19b4c94af083bac (diff)
[Core] Add OS detection callbacks (#21777)
Diffstat (limited to 'quantum')
-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
6 files changed, 379 insertions, 57 deletions
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)