Skip to content

Commit

Permalink
Support setting Usage Page and Usage ID when using hex
Browse files Browse the repository at this point in the history
Previously, the USB Usage Page was assumed to be 0x07, now if you
specify a key using both the USB Usage Page and USB Usage ID it works.
  • Loading branch information
rossmacarthur committed Sep 11, 2024
1 parent 8ad996a commit d79d9d9
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 40 deletions.
42 changes: 22 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,23 +86,23 @@ There are three ways to specify keys:
Some keys you can specify using their name. For example the Return (Enter) key
"⏎" can be specified as "return". These are added on a convenience basis.

| Key | Code | USB Usage ID |
| ------------------ | ----------------------- | ------------ |
| Return (Enter) | `return` | 0x28 |
| Escape | `escape` | 0x29 |
| Delete (Backspace) | `delete` | 0x2A |
| Caps Lock | `capslock` | 0x39 |
| Left Control | `lcontrol` | 0xE0 |
| Left Shift | `lshift` | 0xE1 |
| Left Option | `loption` | 0xE2 |
| Left Command | `lcommand` | 0xE3 |
| Right Control | `rcontrol` | 0xE4 |
| Right Shift | `rshift` | 0xE5 |
| Right Option | `roption` | 0xE6 |
| Right Command | `rcommand` | 0xE7 |
| Function | `fn` | |
| F1, F2, ..., F12 | `f1`, `f2`, ..., `f12` | 0x3A -> 0x45 |
| F13, F14, ..., F24 | `f13`, `f4`, ..., `f24` | 0x68 -> 0x73 |
| Key | Code | USB Usage Page | USB Usage ID |
| ------------------ | ----------------------- | -------------: | -----------: |
| Return (Enter) | `return` | 0x07 | 0x28 |
| Escape | `escape` | 0x07 | 0x29 |
| Delete (Backspace) | `delete` | 0x07 | 0x2A |
| Caps Lock | `capslock` | 0x07 | 0x39 |
| Left Control | `lcontrol` | 0x07 | 0xE0 |
| Left Shift | `lshift` | 0x07 | 0xE1 |
| Left Option | `loption` | 0x07 | 0xE2 |
| Left Command | `lcommand` | 0x07 | 0xE3 |
| Right Control | `rcontrol` | 0x07 | 0xE4 |
| Right Shift | `rshift` | 0x07 | 0xE5 |
| Right Option | `roption` | 0x07 | 0xE6 |
| Right Command | `rcommand` | 0x07 | 0xE7 |
| Function | `fn` | 0xFF | 0x03 |
| F1, F2, ..., F12 | `f1`, `f2`, ..., `f12` | 0x07 | 0x3A -> 0x45 |
| F13, F14, ..., F24 | `f13`, `f4`, ..., `f24` | 0x07 | 0x68 -> 0x73 |

Additionally, the following special names are available and map multiple keys if
they are used.
Expand All @@ -117,13 +117,15 @@ they are used.
### Character

Most typeable keys can be specified using their character. For example: the A
key can be specified using "A" or "a". The USB usage ID used will the one that
key can be specified using "A" or "a". The USB Usage ID used will the one that
the key corresponds to on a US keyboard.

### Number

Any key can be specified by using the USB usage ID in hex. For example: Z has a
usage ID of "0x1d".
Any key can be specified by using the USB Usage ID in hex. For example: Z has a
Usage ID of "0x1d", in this case the USB Usage Page is assumed to be `0x07`
(keyboard). You can also specify the Usage Page and the Usage ID together like
this: "0x7_0000_001D".

## 🤔 Why? How?

Expand Down
3 changes: 2 additions & 1 deletion src/hex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ pub fn parse(s: &str) -> Result<u64> {
let h = s
.strip_prefix("0x")
.ok_or_else(|| anyhow!("{} missing prefix `0x`", s))?;
u64::from_str_radix(h, 16).with_context(|| format!("failed to parse `{}` as hexadecimal", s))
u64::from_str_radix(&h.replace('_', ""), 16)
.with_context(|| format!("failed to parse `{}` as hexadecimal", s))
}
4 changes: 2 additions & 2 deletions src/hid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,9 @@ fn dump_set_option(mappings: &[Map]) -> Result<String> {
s.push(',');
}
s.push('{');
let src = src.usage_page_id() + src.usage_id().ok_or_else(|| err(src))?;
let src = src.usage().ok_or_else(|| err(src))?;
write!(s, "\"HIDKeyboardModifierMappingSrc\":0x{:09x},", src,)?;
let dst = dst.usage_page_id() + dst.usage_id().ok_or_else(|| err(dst))?;
let dst = dst.usage().ok_or_else(|| err(dst))?;
write!(s, "\"HIDKeyboardModifierMappingDst\":0x{:09x}", dst)?;
s.push('}');
}
Expand Down
45 changes: 28 additions & 17 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ pub enum Key {
/// This can be used to represent any key that is not enumerated in this
/// type. See USB HID Usage Tables Specification, Section 10 Keyboard/Keypad
/// Page for exact values for each key.
///
/// If the value is greater than 0x01 << 32, then we assume the value is
/// a usage and includes the usage page.
Raw(u64),
}

Expand Down Expand Up @@ -160,18 +163,24 @@ impl FromStr for Key {
}

impl Key {
/// Returns the usage page ID for this key.
pub fn usage_page_id(&self) -> u64 {
match self {
Key::Fn => 0xff_0000_0000,
_ => 0x7_0000_0000,
/// Returns the usage for this key.
pub fn usage(&self) -> Option<u64> {
Some(self.usage_page() << 32 | self.usage_id()?)
}

/// Returns the usage page for this key.
fn usage_page(&self) -> u64 {
match *self {
Key::Raw(raw) if raw > 0x01 << 32 => 0x00,
Key::Fn => 0xff,
_ => 0x07,
}
}

/// Returns the usage ID for this key.
pub fn usage_id(&self) -> Option<u64> {
fn usage_id(&self) -> Option<u64> {
// https://developer.apple.com/library/archive/technotes/tn2450/_index.html
let usage_id = match self {
let usage_id = match *self {
Self::Return => 0x28,
Self::Escape => 0x29,
Self::Delete => 0x2a,
Expand Down Expand Up @@ -240,7 +249,7 @@ impl Key {
'/' | '?' => 0x38,
_ => return None,
},
&Self::F(num) => match num {
Self::F(num) => match num {
1 => 0x3a,
2 => 0x3b,
3 => 0x3c,
Expand All @@ -267,7 +276,7 @@ impl Key {
24 => 0x73,
_ => unreachable!(),
},
Self::Raw(raw) => *raw,
Self::Raw(raw) => raw,
};
Some(usage_id)
}
Expand Down Expand Up @@ -331,13 +340,15 @@ mod tests {
}

#[test]
fn key_usage_id() {
assert_eq!(Key::Return.usage_id().unwrap(), 0x28);
assert_eq!(Key::Escape.usage_id().unwrap(), 0x29);
assert_eq!(Key::Delete.usage_id().unwrap(), 0x2a);
assert_eq!(Key::CapsLock.usage_id().unwrap(), 0x39);
assert_eq!(Key::F(11).usage_id().unwrap(), 0x44);
assert_eq!(Key::Char('a').usage_id().unwrap(), 0x04);
assert_eq!(Key::Raw(0x5).usage_id().unwrap(), 0x5);
fn key_usage() {
assert_eq!(Key::Return.usage().unwrap(), 0x07_0000_0028);
assert_eq!(Key::Escape.usage().unwrap(), 0x07_0000_0029);
assert_eq!(Key::Delete.usage().unwrap(), 0x07_0000_002a);
assert_eq!(Key::CapsLock.usage().unwrap(), 0x07_0000_0039);
assert_eq!(Key::Fn.usage().unwrap(), 0xff_0000_0003);
assert_eq!(Key::F(11).usage().unwrap(), 0x07_0000_0044);
assert_eq!(Key::Char('a').usage().unwrap(), 0x07_0000_0004);
assert_eq!(Key::Raw(0x5).usage().unwrap(), 0x07_0000_0005);
assert_eq!(Key::Raw(0x7_0000_0005).usage().unwrap(), 0x7_0000_0005);
}
}

0 comments on commit d79d9d9

Please sign in to comment.