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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
//! [`GroupType`] helpers.
#[allow(unused_imports)]
use crate::lookup::{Lookup, LookupTable};
use {
crate::{GroupType, ModifierMask},
hashbrown::HashMap,
std::sync::Arc,
};
pub(crate) mod hidden {
#[allow(unused_imports)]
use {crate::ModifierMask, crate::group_type::GroupTypeBuilder, crate::lookup::Lookup};
use {crate::group_type::Data, std::sync::Arc};
/// The type of a key group.
///
/// Each key group has a type that determines how the active modifiers are mapped to
/// a key level.
///
/// A type has a mask that determines which modifiers are considered by this mapping
/// process. For example, if the mask is [`ModifierMask::SHIFT`], then the state of
/// the control modifier is irrelevant.
///
/// # Example (two-level symbolic keys)
///
/// ```
/// # use kbvm::{GroupType, ModifierMask};
/// let _ty = GroupType::builder(ModifierMask::SHIFT).map(ModifierMask::SHIFT, 1).build();
/// ```
///
/// This type only considers the shift modifier and maps it to level 1. If shift is
/// not pressed, level 0 is used.
///
/// This is the type usually used for two-level symbolic keys. For example, the `1`
/// key on US keyboard layouts that emits `!` when used with shift.
///
/// # Example (two-level alphabetic keys)
///
/// ```
/// # use kbvm::{GroupType, ModifierMask};
/// let _ty = GroupType::builder(ModifierMask::SHIFT | ModifierMask::LOCK)
/// .map(ModifierMask::SHIFT, 1)
/// .map(ModifierMask::LOCK, 1)
/// .build();
/// ```
///
/// This type considers the shift and the capslock modifiers and maps each individual
/// modifier to level 1. If neither modifier is active, the level is 0. If both
/// modifiers are active, the level is also 0, allowing the shift key to cancel the
/// effects of capslock.
///
/// This is the type usually used for two-level alphabetic keys. For example, the `a`
/// key on US keyboard layouts that emits `A` when either shift or capslock are active
/// but reverts back to `a` when shift is pressed while capslock is active.
///
/// # Preserved modifiers
///
/// Usually, mapping modifiers to a level is said to *consume* all modifiers in the
/// type's modifier mask.
///
/// For example, consider the `9` key on German keyboard layout:
///
/// ```text
/// ┌─────────┐
/// │ ) │
/// │ │
/// │ 9 ] │
/// └─────────┘
/// ```
///
/// Depending on the modifiers, this key produces the following keysyms:
///
/// - `None`: `9`
/// - `Shift`: `)`
/// - `AltGr`: `]` (here, `AltGr` is usually an alias for [`ModifierMask::MOD5`])
///
/// The group type for this key thus uses the modifier mask `Shift + AltGr`.
///
/// Now let's say that an application has a keyboard shortcut assigned to `Ctrl + ]`.
/// To trigger the shortcut, the user would have to press the `Ctrl`, `AltGr`, and
/// `9` keys on their keyboard.
///
/// This would cause the effective modifier mask at the time of the key press to be
/// `Ctrl + AltGr`. During the mapping process, the `AltGr` modifier is consumed by
/// the [`GroupType`]. Therefore, the remaining modifiers are `Ctrl`. Since the
/// produced keysym is `]`, the keyboard shortcut is triggered.
///
/// If `AltGr` were not consumed, then the keyboard shortcut would not trigger since
/// the application does not have a shortcut assigned to `Ctrl + AltGr + ]`.
///
/// There are, however, some less common situations where you do not want a modifier
/// to be consumed.
///
/// For example, on Microsoft Windows, keypad keys consider the `shift` modifier but
/// do not consume it. Consider the `←` key on the keypad:
///
/// ```text
/// ┌─────────┐
/// │ 4 │
/// │ │
/// │ ← │
/// └─────────┘
/// ```
///
/// This uses the following mapping:
///
/// - `None`: `←`
/// - `NumLock`: `4`
/// - `Shift`: `←` (preserve `Shift`)
/// - `Shift + NumLock`: `←`
///
/// The group type for this key thus uses the modifier mask `Shift + NumLock` and the
/// shift key can be used to cancel the effects of the num lock key.
///
/// As you can see in the list above, if `NumLock` is not active, we want the `Shift`
/// modifier to be preserved such that applications can use `Shift + ←` as a shortcut.
///
/// You can accomplish this by using the [`GroupTypeBuilder::map_preserve`] function.
#[derive(Clone, Debug)]
pub struct GroupType {
pub(crate) data: Arc<Data>,
}
}
#[derive(Debug)]
pub(crate) struct Data {
pub(crate) mask: ModifierMask,
pub(crate) cases: Vec<Case>,
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct Case {
pub(crate) mods: ModifierMask,
pub(crate) level: usize,
pub(crate) consumed: ModifierMask,
}
#[derive(Debug)]
pub(crate) struct KeyTypeMapping {
pub(crate) level: usize,
pub(crate) consumed: ModifierMask,
}
impl GroupType {
/// Creates a [`GroupTypeBuilder`].
///
/// The mask is the mask that will be considered during the mapping process. See the
/// documentation of [`Self`] for details.
pub fn builder(mask: ModifierMask) -> GroupTypeBuilder {
GroupTypeBuilder {
mask,
cases: Default::default(),
}
}
pub(crate) fn map(&self, mut mods: ModifierMask) -> KeyTypeMapping {
mods &= self.data.mask;
for case in &self.data.cases {
if mods == case.mods {
return KeyTypeMapping {
level: case.level,
consumed: mods & case.consumed,
};
}
}
KeyTypeMapping {
level: 0,
consumed: mods,
}
}
}
/// A builder for a [`GroupType`].
///
/// This type is created via [`GroupType::builder`].
///
/// See the documentation of [`GroupType`] for more information.
#[derive(Clone, Debug)]
pub struct GroupTypeBuilder {
mask: ModifierMask,
cases: HashMap<ModifierMask, Case>,
}
impl GroupTypeBuilder {
/// Builds the [`GroupType`].
pub fn build(&self) -> GroupType {
GroupType {
data: Arc::new(Data {
mask: self.mask,
cases: self.cases.values().copied().collect(),
}),
}
}
/// Adds a mapping from modifiers to a key level.
///
/// If these modifiers already have a mapping, it is replaced.
///
/// This call consumes all modifiers in the types modifier mask during the lookup
/// process. It is equivalent to `self.map_preserve(mods, ModifierMask::NONE, level)`.
pub fn map(&mut self, mods: ModifierMask, level: usize) -> &mut Self {
self.map_preserve(mods, ModifierMask(0), level)
}
/// Add a mapping from modifiers to a key level while preserving some modifiers.
///
/// If these modifiers already have a mapping, it is replaced.
///
/// During the mapping process, the modifiers in `preserved` are not consumed. That
/// is, if the modifiers in a call to [`LookupTable::lookup`] contained any of the
/// modifiers from `preserved`, then [`Lookup::remaining_mods`] will also contain
/// those modifiers.
pub fn map_preserve(
&mut self,
mods: ModifierMask,
preserve: ModifierMask,
level: usize,
) -> &mut Self {
self.cases.insert(
mods,
Case {
mods,
level,
consumed: !preserve,
},
);
self
}
}