Skip to content

Commit 32d15af

Browse files
committed
refactor: clean stale report when switching, remove u8 repr of ConnectionType
Signed-off-by: Haobo Gu <haobogu@outlook.com>
1 parent 4171ebf commit 32d15af

5 files changed

Lines changed: 95 additions & 56 deletions

File tree

rmk-types/src/connection.rs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,6 @@ pub enum ConnectionType {
1616
Ble,
1717
}
1818

19-
/// Unknown stored values default to [`ConnectionType::Usb`] so a downgrade
20-
/// from a newer firmware that wrote an unknown variant falls back to USB
21-
/// rather than refusing to boot.
22-
impl From<u8> for ConnectionType {
23-
fn from(value: u8) -> Self {
24-
match value {
25-
0 => ConnectionType::Usb,
26-
1 => ConnectionType::Ble,
27-
_ => ConnectionType::Usb,
28-
}
29-
}
30-
}
31-
32-
impl From<ConnectionType> for u8 {
33-
fn from(value: ConnectionType) -> Self {
34-
match value {
35-
ConnectionType::Usb => 0,
36-
ConnectionType::Ble => 1,
37-
}
38-
}
39-
}
40-
4119
/// USB device lifecycle. `Suspended` is distinct from `Configured` because
4220
/// the bus is enumerated but transmission is gated on remote wakeup — the
4321
/// first key still needs to reach the USB writer to trigger that wakeup.

rmk/src/ble/profile.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -395,9 +395,7 @@ impl<'a, C: Controller + ControllerCmdAsync<LeSetPhy>, P: PacketPool> ProfileMan
395395
info!("Switching preferred transport to: {:?}", updated);
396396

397397
#[cfg(feature = "storage")]
398-
FLASH_CHANNEL
399-
.send(FlashOperationMessage::ConnectionType(updated.into()))
400-
.await;
398+
FLASH_CHANNEL.send(FlashOperationMessage::ConnectionType(updated)).await;
401399
}
402400
}
403401
#[cfg(feature = "storage")]

rmk/src/channel.rs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use {crate::ble::profile::BleProfileAction, rmk_types::led_indicator::LedIndicat
1212

1313
#[cfg(feature = "host")]
1414
use crate::VIAL_CHANNEL_SIZE;
15-
use crate::hid::Report;
15+
use crate::hid::{KeyboardReport, Report};
1616
#[cfg(feature = "storage")]
1717
use crate::{FLASH_CHANNEL_SIZE, storage::FlashOperationMessage};
1818
use crate::{REPORT_CHANNEL_SIZE, RawMutex};
@@ -32,17 +32,22 @@ pub static USB_REPORT_CHANNEL: ReportChannel = Channel::new();
3232
#[cfg(feature = "_ble")]
3333
pub static BLE_REPORT_CHANNEL: ReportChannel = Channel::new();
3434

35-
fn active_report_channel() -> Option<(ConnectionType, &'static ReportChannel)> {
36-
match crate::state::active_transport()? {
35+
fn report_channel(transport: ConnectionType) -> Option<&'static ReportChannel> {
36+
match transport {
3737
#[cfg(not(feature = "_no_usb"))]
38-
ConnectionType::Usb => Some((ConnectionType::Usb, &USB_REPORT_CHANNEL)),
38+
ConnectionType::Usb => Some(&USB_REPORT_CHANNEL),
3939
#[cfg(feature = "_ble")]
40-
ConnectionType::Ble => Some((ConnectionType::Ble, &BLE_REPORT_CHANNEL)),
40+
ConnectionType::Ble => Some(&BLE_REPORT_CHANNEL),
4141
#[allow(unreachable_patterns)]
4242
_ => None,
4343
}
4444
}
4545

46+
fn active_report_channel() -> Option<(ConnectionType, &'static ReportChannel)> {
47+
let transport = crate::state::active_transport()?;
48+
report_channel(transport).map(|ch| (transport, ch))
49+
}
50+
4651
/// Reports generated while no transport is selected are dropped on the floor.
4752
pub(crate) async fn send_hid_report(mut report: Report) {
4853
let Some((transport, ch)) = active_report_channel() else {
@@ -71,16 +76,13 @@ pub(crate) fn try_send_hid_report(report: Report) {
7176
}
7277
}
7378

74-
/// Drains queued reports for `transport`. Called on active-transport flips so
75-
/// a future re-activation doesn't replay stale presses without their releases.
76-
pub(crate) fn clear_report_channel(transport: ConnectionType) {
77-
match transport {
78-
#[cfg(not(feature = "_no_usb"))]
79-
ConnectionType::Usb => USB_REPORT_CHANNEL.clear(),
80-
#[cfg(feature = "_ble")]
81-
ConnectionType::Ble => BLE_REPORT_CHANNEL.clear(),
82-
#[allow(unreachable_patterns)]
83-
_ => {}
79+
/// Drains queued reports for `transport` and leaves an all-up keyboard report
80+
/// for its writer. Called on active-transport flips so the previous host
81+
/// releases any pressed keys without replaying stale queued reports later.
82+
pub(crate) fn clear_and_release_report_channel(transport: ConnectionType) {
83+
if let Some(ch) = report_channel(transport) {
84+
ch.clear();
85+
let _ = ch.try_send(Report::KeyboardReport(KeyboardReport::default()));
8486
}
8587
}
8688

rmk/src/state.rs

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ fn update_status(f: impl FnOnce(&mut ConnectionStatus)) {
7070
// Drain after the commit so any producer racing past the mutex reads
7171
// the new state and routes to the new channel rather than the one
7272
// about to be cleared.
73-
crate::channel::clear_report_channel(prev_active);
73+
crate::channel::clear_and_release_report_channel(prev_active);
7474
}
7575

7676
#[cfg(feature = "_ble")]
@@ -119,9 +119,9 @@ pub(crate) async fn load_preferred_connection() -> ConnectionType {
119119
#[cfg(feature = "storage")]
120120
let stored = crate::storage::read_connection_type().await;
121121
#[cfg(not(feature = "storage"))]
122-
let stored: Option<u8> = None;
122+
let stored: Option<ConnectionType> = None;
123123
match stored {
124-
Some(c) => c.into(),
124+
Some(c) => c,
125125
#[cfg(feature = "_no_usb")]
126126
None => ConnectionType::Ble,
127127
#[cfg(not(feature = "_no_usb"))]
@@ -160,6 +160,7 @@ mod tests {
160160
set_ble_state, set_preferred_connection, set_usb_state,
161161
};
162162
use crate::event::{ConnectionChangeEvent, EventSubscriber, SubscribableEvent};
163+
use crate::hid::{KeyboardReport, Report};
163164
use crate::test_support::test_block_on as block_on;
164165

165166
fn state_test_lock() -> &'static Mutex<()> {
@@ -176,6 +177,27 @@ mod tests {
176177
crate::channel::BLE_REPORT_CHANNEL.clear();
177178
}
178179

180+
fn pressed_keyboard_report() -> Report {
181+
Report::KeyboardReport(KeyboardReport {
182+
modifier: 0x02,
183+
reserved: 0,
184+
leds: 0,
185+
keycodes: [4, 0, 0, 0, 0, 0],
186+
})
187+
}
188+
189+
fn assert_all_up_keyboard_report(report: Report) {
190+
match report {
191+
Report::KeyboardReport(r) => {
192+
assert_eq!(r.modifier, 0);
193+
assert_eq!(r.reserved, 0);
194+
assert_eq!(r.leds, 0);
195+
assert_eq!(r.keycodes, [0; 6]);
196+
}
197+
_ => panic!("expected keyboard all-up report"),
198+
}
199+
}
200+
179201
#[cfg(not(feature = "_no_usb"))]
180202
#[test]
181203
fn usb_builds_keep_input_processing_active_without_transport() {
@@ -223,9 +245,8 @@ mod tests {
223245

224246
#[cfg(not(feature = "_no_usb"))]
225247
#[test]
226-
fn flipping_away_from_active_clears_its_report_channel() {
248+
fn flipping_away_from_active_clears_stale_reports_and_queues_all_up() {
227249
use crate::channel::USB_REPORT_CHANNEL;
228-
use crate::hid::{KeyboardReport, Report};
229250

230251
let _guard = state_test_lock().lock().unwrap();
231252
reset_state();
@@ -236,18 +257,23 @@ mod tests {
236257
// that would otherwise persist across a flip.
237258
USB_REPORT_CHANNEL.clear();
238259
USB_REPORT_CHANNEL
239-
.try_send(Report::KeyboardReport(KeyboardReport::default()))
260+
.try_send(pressed_keyboard_report())
240261
.expect("channel should have capacity for sentinel");
241262
assert!(USB_REPORT_CHANNEL.try_receive().is_ok());
242263
USB_REPORT_CHANNEL
243-
.try_send(Report::KeyboardReport(KeyboardReport::default()))
264+
.try_send(pressed_keyboard_report())
244265
.expect("channel should have capacity for sentinel");
245266

246267
set_usb_state(UsbState::Disabled);
247268
assert!(super::active_transport().is_none());
269+
assert_all_up_keyboard_report(
270+
USB_REPORT_CHANNEL
271+
.try_receive()
272+
.expect("USB_REPORT_CHANNEL should contain keyboard all-up report"),
273+
);
248274
assert!(
249275
USB_REPORT_CHANNEL.try_receive().is_err(),
250-
"USB_REPORT_CHANNEL should be drained when USB stops being active"
276+
"USB_REPORT_CHANNEL should contain only the all-up report"
251277
);
252278
}
253279

@@ -257,15 +283,14 @@ mod tests {
257283
use embassy_futures::join::join;
258284

259285
use crate::channel::{USB_REPORT_CHANNEL, send_hid_report};
260-
use crate::hid::{KeyboardReport, Report};
261286

262287
let _guard = state_test_lock().lock().unwrap();
263288
reset_state();
264289
set_usb_state(UsbState::Configured);
265290

266291
for _ in 0..crate::REPORT_CHANNEL_SIZE {
267292
USB_REPORT_CHANNEL
268-
.try_send(Report::KeyboardReport(KeyboardReport::default()))
293+
.try_send(pressed_keyboard_report())
269294
.expect("channel should have capacity while filling");
270295
}
271296

@@ -277,7 +302,43 @@ mod tests {
277302
},
278303
));
279304

280-
assert!(USB_REPORT_CHANNEL.try_receive().is_err());
305+
assert_all_up_keyboard_report(
306+
USB_REPORT_CHANNEL
307+
.try_receive()
308+
.expect("USB_REPORT_CHANNEL should contain keyboard all-up report"),
309+
);
310+
assert!(
311+
USB_REPORT_CHANNEL.try_receive().is_err(),
312+
"USB_REPORT_CHANNEL should contain only the all-up report"
313+
);
314+
}
315+
316+
#[cfg(all(not(feature = "_no_usb"), feature = "_ble"))]
317+
#[test]
318+
fn usb_preference_flip_releases_previous_ble_transport() {
319+
use crate::channel::BLE_REPORT_CHANNEL;
320+
321+
let _guard = state_test_lock().lock().unwrap();
322+
reset_state();
323+
set_preferred_connection(ConnectionType::Usb);
324+
set_ble_state(BleState::Connected);
325+
assert_eq!(super::active_transport(), Some(ConnectionType::Ble));
326+
327+
BLE_REPORT_CHANNEL
328+
.try_send(pressed_keyboard_report())
329+
.expect("BLE report channel should have capacity for sentinel");
330+
331+
set_usb_state(UsbState::Configured);
332+
assert_eq!(super::active_transport(), Some(ConnectionType::Usb));
333+
assert_all_up_keyboard_report(
334+
BLE_REPORT_CHANNEL
335+
.try_receive()
336+
.expect("BLE_REPORT_CHANNEL should contain keyboard all-up report"),
337+
);
338+
assert!(
339+
BLE_REPORT_CHANNEL.try_receive().is_err(),
340+
"BLE_REPORT_CHANNEL should contain only the all-up report"
341+
);
281342
}
282343

283344
#[test]
@@ -305,7 +366,6 @@ mod tests {
305366
use embassy_futures::join::join;
306367

307368
use crate::channel::{USB_REPORT_CHANNEL, send_hid_report};
308-
use crate::hid::{KeyboardReport, Report};
309369

310370
let _guard = state_test_lock().lock().unwrap();
311371
reset_state();

rmk/src/storage/mod.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use embassy_time::Duration;
66
use embedded_storage::nor_flash::NorFlash;
77
use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
88
use postcard::experimental::max_size::MaxSize;
9+
use rmk_types::connection::ConnectionType;
910
use rmk_types::morse::MorseProfile;
1011
use sequential_storage::Error as SSError;
1112
use sequential_storage::cache::NoCache;
@@ -40,7 +41,7 @@ static BOND_INFO_RESPONSE: Signal<crate::RawMutex, Option<ProfileInfo>> = Signal
4041
#[cfg(all(feature = "_ble", feature = "split"))]
4142
static PEER_ADDRESS_RESPONSE: Signal<crate::RawMutex, Option<PeerAddress>> = Signal::new();
4243
#[cfg(feature = "_ble")]
43-
static CONNECTION_TYPE_RESPONSE: Signal<crate::RawMutex, Option<u8>> = Signal::new();
44+
static CONNECTION_TYPE_RESPONSE: Signal<crate::RawMutex, Option<ConnectionType>> = Signal::new();
4445
#[cfg(feature = "_ble")]
4546
static ACTIVE_BLE_PROFILE_RESPONSE: Signal<crate::RawMutex, Option<u8>> = Signal::new();
4647

@@ -62,7 +63,7 @@ pub(crate) async fn read_peer_address(peer_id: u8) -> Option<PeerAddress> {
6263
}
6364

6465
#[cfg(feature = "_ble")]
65-
pub(crate) async fn read_connection_type() -> Option<u8> {
66+
pub(crate) async fn read_connection_type() -> Option<ConnectionType> {
6667
request_read(FlashOperationMessage::ReadConnectionType, &CONNECTION_TYPE_RESPONSE).await
6768
}
6869

@@ -140,7 +141,7 @@ pub(crate) enum FlashOperationMessage {
140141
morse: Morse,
141142
},
142143
// Current saved connection type
143-
ConnectionType(u8),
144+
ConnectionType(ConnectionType),
144145
// Timeout time for combos
145146
ComboTimeout(u16),
146147
// Timeout time for one-shot keys
@@ -261,7 +262,7 @@ pub(crate) enum StorageData {
261262
StorageConfig(LocalStorageConfig),
262263
LayoutConfig(LayoutConfig),
263264
BehaviorConfig(BehaviorConfig),
264-
ConnectionType(u8),
265+
ConnectionType(ConnectionType),
265266
#[cfg(feature = "host")]
266267
MacroData(#[serde(with = "crate::host::storage::macro_bytes_serde")] [u8; MACRO_SPACE_SIZE]),
267268
#[cfg(feature = "host")]

0 commit comments

Comments
 (0)