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
231
232
233
234
235
236
237
238
239
240
#![recursion_limit = "256"]
use quote::quote;
use regex::{Captures, Regex};
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("lib.rs");
let keycode_converter_data = format!(
"
{}
{}
",
quote! {
/// The mapping of values between platforms for a specific key
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
pub enum KeyMapping {
/// USB HID value for a specific key
Usb(u16),
/// Linux kernel evdev value for a specific key
Evdev(u16),
/// X11 value for a specific key
Xkb(u16),
/// Windows value for a specific key
Win(u16),
/// Mac value for a specific key
Mac(u16),
/// W3 browser event code for a specific key
Code(KeyMappingCode),
/// Id for a specific key
Id(KeyMappingId),
}
/// Ergonomic access to a specific key's mapping of values
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
pub struct KeyMap {
/// USB HID value for a specific key
pub usb: u16,
/// Linux kernel evdev value for a specific key
pub evdev: u16,
/// X11 value for a specific key
pub xkb: u16,
/// Windows value for a specific key
pub win: u16,
/// Mac value for a specific key
pub mac: u16,
/// W3 browser event code for a specific key
pub code: KeyMappingCode,
/// Id for a specific key
pub id: KeyMappingId,
/// USB HID bitmask
pub modifier: Option<KeyModifiers>,
}
impl KeyMap {
/// If you don't want to use TryFrom, until it is stabilized
pub fn from_key_mapping(key_mapping: KeyMapping) -> Result<KeyMap, ()> {
get_key_map(key_mapping)
}
}
impl TryFrom<KeyMapping> for KeyMap {
type Error = ();
fn try_from(key_mapping: KeyMapping) -> Result<KeyMap, Self::Error> {
get_key_map(key_mapping)
}
}
macro_rules! USB_KEYMAP_DECLARATION {
{
$(USB_KEYMAP($usb:expr, $evdev:expr, $xkb:expr, $win:expr, $mac:expr, $code:ident, $id:ident),)*
} => {
fn get_key_map(key_mapping: KeyMapping) -> Result<KeyMap, ()> {
#[allow(unreachable_patterns)]
match key_mapping {
$(
KeyMapping::Usb($usb) | KeyMapping::Evdev($evdev) | KeyMapping::Xkb($xkb) | KeyMapping::Win($win) | KeyMapping::Mac($mac) | KeyMapping::Code(KeyMappingCode::$code) | KeyMapping::Id(KeyMappingId::$id) => {
let id = KeyMappingId::$id;
let keymap = KeyMap {
usb: $usb,
evdev: $evdev,
xkb: $xkb,
win: $win,
mac: $mac,
code: KeyMappingCode::$code,
modifier: match id {
KeyMappingId::CONTROL_LEFT => Some(KeyModifiers::CONTROL_LEFT),
KeyMappingId::SHIFT_LEFT => Some(KeyModifiers::SHIFT_LEFT),
KeyMappingId::ALT_LEFT => Some(KeyModifiers::ALT_LEFT),
KeyMappingId::META_LEFT => Some(KeyModifiers::META_LEFT),
KeyMappingId::CONTROL_RIGHT => Some(KeyModifiers::CONTROL_RIGHT),
KeyMappingId::SHIFT_RIGHT => Some(KeyModifiers::SHIFT_RIGHT),
KeyMappingId::ALT_RIGHT => Some(KeyModifiers::ALT_RIGHT),
KeyMappingId::META_RIGHT => Some(KeyModifiers::META_RIGHT),
_ => None,
},
id,
};
Ok(keymap)
},
)*
_ => Err(())
}
}
/// W3 browser event code for a specific key
///
/// <https://www.w3.org/TR/uievents-code/>
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
pub enum KeyMappingCode {
$(
#[doc = "W3 browser event code for a specific key"]
$code,
)*
}
impl core::fmt::Display for KeyMappingCode {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match *self {
$(
KeyMappingCode::$code => write!(f, stringify!($code)),
)*
}
}
}
impl From<KeyMappingCode> for KeyMap {
fn from(code: KeyMappingCode) -> KeyMap {
get_key_map(KeyMapping::Code(code)).unwrap()
}
}
/// Id for a specific key
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
pub enum KeyMappingId {
$(
#[doc = "Id for a specific key"]
#[allow(non_camel_case_types)]
$id,
)*
}
impl core::fmt::Display for KeyMappingId {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match *self {
$(
KeyMappingId::$id => write!(f, stringify!($id)),
)*
}
}
}
impl From<KeyMappingId> for KeyMap {
fn from(id: KeyMappingId) -> KeyMap {
get_key_map(KeyMapping::Id(id)).unwrap()
}
}
}
}
},
{
let mut file = include_str!("keycode_converter_data.inc").to_string();
// Remove any existing macros
file = Regex::new("(?m)^#(if|define|include|undef|endif).*?$")
.unwrap()
.replace_all(&file, "")
.to_string();
// Make variable into macro
file = Regex::new("(USB_KEYMAP_DECLARATION)")
.unwrap()
.replace_all(&file, "$1!")
.to_string();
// Macros don't have semicolons
file = Regex::new(r"(\});")
.unwrap()
.replace_all(&file, "$1")
.to_string();
// Ignore HID usage page + fix for linting
file = Regex::new(r"(USB_KEYMAP\(0x)..(....)")
.unwrap()
.replace_all(&file, "$1$2")
.to_string();
// Make codes idents, but don't replace the quotes in comments
let comment_prefix = r"//";
let quote_char = "\"";
let quote_placeholder = r"#####";
let comment_quote_regex =
Regex::new(format!("{}(?P<a>.*?){}", comment_prefix, quote_char).as_str()).unwrap();
let comment_quote_placeholder_regex =
Regex::new(format!("{}(?P<a>.*?){}", comment_prefix, quote_placeholder).as_str())
.unwrap();
let all_quotes_regex = Regex::new(quote_char).unwrap();
while comment_quote_regex.is_match(&file) {
file = comment_quote_regex
.replace_all(
&file,
format!("{}$a{}", comment_prefix, quote_placeholder).as_str(),
)
.to_string();
}
file = all_quotes_regex.replace_all(&file, "").to_string();
while comment_quote_placeholder_regex.is_match(&file) {
file = comment_quote_placeholder_regex
.replace_all(
&file,
format!("{}$a{}", comment_prefix, quote_char).as_str(),
)
.to_string();
}
// Make NULL into a unique ident
let mut counter = 0;
file = Regex::new("NULL")
.unwrap()
.replace_all(&file, move |_: &Captures| {
counter += 1;
format!("Null{}", counter)
})
.to_string();
file
}
);
println!("{}", keycode_converter_data);
let mut file = File::create(&dest_path).unwrap();
file.write_all(keycode_converter_data.as_bytes()).unwrap();
}