1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
use embassy_time::Instant;
use rmk_types::modifier::ModifierCombination;
use crate::event::KeyboardEvent;
use crate::keyboard::Keyboard;
/// State machine for one shot keys
#[derive(Default)]
pub enum OneShotState<T> {
/// First one shot key press
Initial(T),
/// One shot key was released before any other key, normal one shot behavior
Single(T),
/// Another key was pressed before one shot key was released, treat as a normal modifier/layer
Held(T),
/// One shot inactive
#[default]
None,
}
impl<T> OneShotState<T> {
/// Get the current one shot value if any
pub fn value(&self) -> Option<&T> {
match self {
OneShotState::Initial(v) | OneShotState::Single(v) | OneShotState::Held(v) => Some(v),
OneShotState::None => None,
}
}
}
impl<'a> Keyboard<'a> {
pub(crate) async fn process_action_osm(&mut self, new_modifiers: ModifierCombination, event: KeyboardEvent) {
let activate_on_keypress = self.keymap.one_shot_modifiers_config().activate_on_keypress;
// Update one shot state
if event.pressed {
// Any pending expiry deadline belongs to a previous `Single` state; the
// matching release re-arms a fresh one if the state stays `Single`.
self.osm_deadline = None;
let mut was_active = false;
// Add new modifier combination to existing one shot or init if none
self.osm_state = match self.osm_state {
OneShotState::None => OneShotState::Initial(new_modifiers),
OneShotState::Initial(cur_modifiers) => OneShotState::Initial(cur_modifiers | new_modifiers),
OneShotState::Single(cur_modifiers) => {
was_active = cur_modifiers & new_modifiers == new_modifiers;
if was_active {
let result = cur_modifiers & !new_modifiers;
// Send report for current osm_state modifiers
self.send_keyboard_report_with_resolved_modifiers(true).await;
if result.into_bits() == 0 {
OneShotState::None
} else {
OneShotState::Single(result)
}
} else {
OneShotState::Single(cur_modifiers | new_modifiers)
}
}
OneShotState::Held(cur_modifiers) => OneShotState::Held(cur_modifiers | new_modifiers),
};
self.update_osl(event);
// Send report for updated osm_state modifiers
if was_active || activate_on_keypress {
self.send_keyboard_report_with_resolved_modifiers(true).await;
}
} else {
match self.osm_state {
OneShotState::Initial(cur_modifiers) | OneShotState::Single(cur_modifiers) => {
// Released before any other key: keep the modifiers armed for the
// next keypress. Expiry is deadline-driven from `run()`, so the
// keyboard task keeps serving other events in the meantime.
self.osm_state = OneShotState::Single(cur_modifiers);
self.osm_deadline = Some(Instant::now() + self.keymap.one_shot_timeout());
}
OneShotState::Held(cur_modifiers) => {
let was_active = cur_modifiers & new_modifiers == new_modifiers;
if !was_active {
return;
}
// Release modifier
self.update_osl(event);
self.osm_state = OneShotState::None;
// This sends a separate hid report with the
// currently registered modifiers except the
// one shot modifiers -> this way "releasing" them.
self.send_keyboard_report_with_resolved_modifiers(false).await;
}
_ => (),
};
}
}
pub(crate) async fn process_action_osl(&mut self, layer_num: u8, event: KeyboardEvent) {
// Update one shot state
if event.pressed {
// Any pending expiry deadline belongs to a previous `Single` state
self.osl_deadline = None;
// Deactivate old layer if any
if let Some(&l) = self.osl_state.value() {
self.keymap.deactivate_layer(l);
}
// Update layer of one shot
self.osl_state = match self.osl_state {
OneShotState::None => OneShotState::Initial(layer_num),
OneShotState::Initial(_) => OneShotState::Initial(layer_num),
OneShotState::Single(_) => OneShotState::Single(layer_num),
OneShotState::Held(_) => OneShotState::Held(layer_num),
};
// Activate new layer
self.keymap.activate_layer(layer_num);
} else {
match self.osl_state {
OneShotState::Initial(l) | OneShotState::Single(l) => {
// Released before any other key: keep the layer armed for the
// next keypress. Expiry is deadline-driven from `run()`, so the
// keyboard task keeps serving other events in the meantime.
self.osl_state = OneShotState::Single(l);
self.osl_deadline = Some(Instant::now() + self.keymap.one_shot_timeout());
}
OneShotState::Held(layer_num) => {
self.osl_state = OneShotState::None;
self.keymap.deactivate_layer(layer_num);
}
_ => (),
};
}
}
/// Update OSM state based on the keyboard event.
/// Returns `true` if the OSM was consumed (transitioned from Single to None).
pub(crate) fn update_osm(&mut self, event: KeyboardEvent) -> bool {
match self.osm_state {
OneShotState::Initial(m) => {
self.osm_state = OneShotState::Held(m);
false
}
// Consume on press so a key rolled over before this one releases doesn't
// also see the modifier (resolve_explicit_modifiers only applies `Single`
// on the pressed report anyway, so the release report is unaffected).
OneShotState::Single(_) if event.pressed => {
self.osm_state = OneShotState::None;
self.osm_deadline = None;
true
}
_ => false,
}
}
pub(crate) fn update_osl(&mut self, event: KeyboardEvent) {
match self.osl_state {
OneShotState::Initial(l) => self.osl_state = OneShotState::Held(l),
OneShotState::Single(layer_num) if !event.pressed => {
self.osl_deadline = None;
self.keymap.deactivate_layer(layer_num);
self.osl_state = OneShotState::None;
}
_ => (),
}
}
/// Fire expired one-shot deadlines: disarm timed-out one-shot modifiers and
/// deactivate the timed-out one-shot layer. Called from `run()`'s deadline
/// race; each deadline is re-checked against the current time, so a call
/// before expiry is a no-op.
pub(crate) async fn fire_oneshot_timeout(&mut self) {
let now = Instant::now();
if self.osm_deadline.is_some_and(|d| d <= now) {
self.osm_deadline = None;
self.osm_state = OneShotState::None;
// Send release report because modifiers were reported as held on press
if self.keymap.one_shot_modifiers_config().activate_on_keypress {
self.send_keyboard_report_with_resolved_modifiers(false).await;
}
}
if self.osl_deadline.is_some_and(|d| d <= now) {
self.osl_deadline = None;
// Deactivate the stored layer, not the triggering event's layer
if let OneShotState::Single(l) = self.osl_state {
self.keymap.deactivate_layer(l);
}
self.osl_state = OneShotState::None;
}
}
}