-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
bad_usb: add new keyboard layout file format
The existing .kl file format is quite limited. It only supports ASCII characters, and it assumes all characters can be pressed using a single key on the user's keyboard. The latter assumption is false even when just considering ASCII characters. For example, the US-International keyboard layout, which is the de facto standard keyboard layout in the Netherlands, uses dead keys for the following characters: ` ~ ^ ' ". To type any of these characters, one needs to press the same key as in the ANSI QWERTY layout, but followed by a space. This is not possible to describe in the current keyboard layout, thus making Dutch computers immune to Bad USB. The new file format makes this possible, by mapping characters to a sequence of keys, rather than just a single key. It also expands the format to support any Unicode codepoint, thus unlocking Bad USB to a much wider audience. Characters composed of multiple codepoints are not supported though, thus while this does make it possible to support é when encoded as U+00E9 LATIN SMALL LETTER E WITH ACUTE, it does not support é when encoded as U+0065 LATIN SMALL LETTER E + U+0301 COMBINING ACUTE ACCENT. The new file format is implemented in a fully backwards compatible way, allowing users to keep using existing .kl files they may have added. The documentation of the new file format can be found in documentation/file_formats/BadUsbKeyboardFormat.md. This commit also adds the keyboard layout en-US-intl.kl, which implements the US-International keyboard layout. Signed-off-by: Guacamolie <[email protected]>
- Loading branch information
1 parent
1070064
commit 7ce3f5b
Showing
10 changed files
with
542 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
#include <furi_hal.h> | ||
#include <stdint.h> | ||
#include <storage/storage.h> | ||
#include "keyboard.h" | ||
#include "keyboard_i.h" | ||
|
||
const uint8_t header_magic[3] = {0xFF, 'K', 'L'}; | ||
|
||
static int determine_version(File* layout_file) { | ||
struct SharedHeader shared_header = {0}; | ||
storage_file_read(layout_file, &shared_header, sizeof(shared_header)); | ||
storage_file_seek(layout_file, 0, true); | ||
|
||
if(memcmp( | ||
shared_header.magic, | ||
header_magic, | ||
MIN(sizeof(shared_header.magic), sizeof(header_magic))) != 0) { | ||
// Backwards compat with v1 | ||
return 1; | ||
} | ||
return shared_header.version; | ||
} | ||
|
||
BadUsbKeyboard* bad_usb_keyboard_alloc_read(File* layout_file) { | ||
switch(determine_version(layout_file)) { | ||
case 1: | ||
return bad_usb_keyboard_v1_alloc_read(layout_file); | ||
case 2: | ||
return bad_usb_keyboard_v2_alloc_read(layout_file); | ||
default: | ||
return NULL; | ||
} | ||
} | ||
|
||
BadUsbKeyboard* bad_usb_keyboard_alloc(void* data, const struct BadUsbKeyboardVTable* vtable) { | ||
// vtable->get_key_sequence is optional | ||
furi_assert(vtable); | ||
furi_assert(vtable->get_key); | ||
furi_assert(vtable->free); | ||
|
||
BadUsbKeyboard* kb = malloc(sizeof(BadUsbKeyboard)); | ||
kb->data = data; | ||
kb->vtable = vtable; | ||
return kb; | ||
} | ||
|
||
void bad_usb_keyboard_free(BadUsbKeyboard* keyboard) { | ||
furi_assert(keyboard); | ||
|
||
keyboard->vtable->free(keyboard->data); | ||
free(keyboard); | ||
} | ||
|
||
void bad_usb_keyboard_type_codepoint(BadUsbKeyboard* keyboard, FuriStringUnicodeValue codepoint) { | ||
furi_assert(keyboard); | ||
|
||
if(keyboard->vtable->get_key_sequence) { | ||
uint16_t* key_sequence = keyboard->vtable->get_key_sequence(keyboard->data, codepoint); | ||
|
||
if(key_sequence == NULL) { | ||
return; | ||
} | ||
|
||
for(size_t i = 0; key_sequence[i] != HID_KEYBOARD_NONE; i++) { | ||
furi_hal_hid_kb_press(key_sequence[i]); | ||
furi_hal_hid_kb_release(key_sequence[i]); | ||
} | ||
} else { | ||
uint16_t key = keyboard->vtable->get_key(keyboard->data, codepoint); | ||
if(key != HID_KEYBOARD_NONE) { | ||
furi_hal_hid_kb_press(key); | ||
furi_hal_hid_kb_release(key); | ||
} | ||
} | ||
} | ||
|
||
void bad_usb_keyboard_type_string(BadUsbKeyboard* keyboard, const char* string) { | ||
furi_assert(keyboard); | ||
|
||
FuriStringUTF8State utf8_state = FuriStringUTF8StateStarting; | ||
FuriStringUnicodeValue codepoint; | ||
|
||
for(size_t i = 0; string[i] != '\0'; i++) { | ||
furi_string_utf8_decode(string[i], &utf8_state, &codepoint); | ||
if(utf8_state != FuriStringUTF8StateStarting) { | ||
continue; | ||
} | ||
|
||
bad_usb_keyboard_type_codepoint(keyboard, codepoint); | ||
} | ||
} | ||
|
||
uint16_t bad_usb_keyboard_get_key(BadUsbKeyboard* keyboard, FuriStringUnicodeValue codepoint) { | ||
furi_assert(keyboard); | ||
|
||
return keyboard->vtable->get_key(keyboard->data, codepoint); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#pragma once | ||
|
||
#ifdef __cplusplus | ||
extern "C" { | ||
#endif | ||
|
||
#include <furi.h> | ||
#include <storage/storage.h> | ||
|
||
typedef struct BadUsbKeyboard BadUsbKeyboard; | ||
|
||
BadUsbKeyboard* bad_usb_keyboard_alloc_default(); | ||
BadUsbKeyboard* bad_usb_keyboard_alloc_read(File* layout_file); | ||
void bad_usb_keyboard_free(BadUsbKeyboard* keyboard); | ||
|
||
void bad_usb_keyboard_type_string(BadUsbKeyboard* keyboard, const char* string); | ||
void bad_usb_keyboard_type_codepoint(BadUsbKeyboard* keyboard, FuriStringUnicodeValue codepoint); | ||
|
||
uint16_t bad_usb_keyboard_get_key(BadUsbKeyboard* keyboard, FuriStringUnicodeValue codepoint); | ||
|
||
#ifdef __cplusplus | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
#include <furi_hal.h> | ||
#include <stdint.h> | ||
#include <storage/storage.h> | ||
#include "keyboard_i.h" | ||
|
||
static uint16_t get_key(void*, FuriStringUnicodeValue codepoint) { | ||
if(codepoint >= sizeof(hid_asciimap) / sizeof(*hid_asciimap)) { | ||
return HID_KEYBOARD_NONE; | ||
} | ||
|
||
return hid_asciimap[codepoint]; | ||
} | ||
|
||
static void free_data(void*) { | ||
// No-op. | ||
} | ||
|
||
static const struct BadUsbKeyboardVTable vtable = { | ||
.get_key = get_key, | ||
.free = free_data, | ||
}; | ||
|
||
BadUsbKeyboard* bad_usb_keyboard_alloc_default() { | ||
return bad_usb_keyboard_alloc(NULL, &vtable); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
#pragma once | ||
|
||
#include <stdint.h> | ||
#ifdef __cplusplus | ||
extern "C" { | ||
#endif | ||
|
||
#include <storage/storage.h> | ||
#include "keyboard.h" | ||
|
||
struct BadUsbKeyboardVTable { | ||
/** | ||
* @brief Gets the key associated the codepoint. | ||
* | ||
* This gets the key that needs to be pressed to enter the character | ||
* associated with codepoint. The key is represented by a 2 byte value with | ||
* the USB HID Keyboard/Keypad Page Usage ID in the low byte and the | ||
* modifier keys in the high byte. It is suitable for passing to | ||
* furi_hal_hid_kb_press. | ||
* | ||
* This function is required to be implemented. | ||
* | ||
* @returns The keycode if the codepoint can meaningfully be entered by | ||
* pressing a single key. Otherwise returns HID_KEYBOARD_NONE. | ||
*/ | ||
uint16_t (*get_key)(void* data, FuriStringUnicodeValue codepoint); | ||
|
||
/** | ||
* @brief Gets the sequence of keys associated with the codepoint. | ||
* | ||
* This gets the key sequence that needs to be pressed to enter the | ||
* character associated with the codepoint. For example, to enter the | ||
* codepoint U+00E9 “é” LATIN SMALL LETTER E WITH ACUTE on the | ||
* US-International keyboard layout, one first needs to type | ||
* HID_KEYBOARD_APOSTROPHE (0x34), followed by HID_KEYBOARD_E (0x08). | ||
* | ||
* This function is optional. It may be set to NULL, in which case get_key | ||
* will be used instead. | ||
* | ||
* @returns A pointer to a sequence of keycodes, terminated by | ||
* HID_KEYBOARD_NONE. NULL may be returned if no sequence could be found. | ||
*/ | ||
uint16_t* (*get_key_sequence)(void* data, FuriStringUnicodeValue codepoint); | ||
|
||
/** | ||
* @brief Called when the data structure needs to be freed. | ||
*/ | ||
void (*free)(void* data); | ||
}; | ||
|
||
struct BadUsbKeyboard { | ||
const struct BadUsbKeyboardVTable* vtable; | ||
void* data; | ||
}; | ||
|
||
BadUsbKeyboard* bad_usb_keyboard_alloc(void* data, const struct BadUsbKeyboardVTable* vtable); | ||
|
||
BadUsbKeyboard* bad_usb_keyboard_layout_default_alloc(); | ||
BadUsbKeyboard* bad_usb_keyboard_v1_alloc_read(File* layout); | ||
BadUsbKeyboard* bad_usb_keyboard_v2_alloc_read(File* layout); | ||
|
||
struct SharedHeader { | ||
uint8_t magic[3]; | ||
uint8_t version; | ||
} FURI_PACKED; | ||
|
||
#ifdef __cplusplus | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
#include <furi_hal.h> | ||
#include <storage/storage.h> | ||
#include "keyboard.h" | ||
#include "keyboard_i.h" | ||
|
||
struct Layout { | ||
uint16_t keycodes[128]; | ||
}; | ||
|
||
static uint16_t get_key(void* data, FuriStringUnicodeValue codepoint) { | ||
struct Layout* layout = data; | ||
|
||
if(codepoint >= sizeof(layout->keycodes) / sizeof(*layout->keycodes)) { | ||
return HID_KEYBOARD_NONE; | ||
} | ||
|
||
return layout->keycodes[codepoint]; | ||
} | ||
|
||
static const struct BadUsbKeyboardVTable imp_v1 = { | ||
.get_key = get_key, | ||
.free = free, | ||
}; | ||
|
||
BadUsbKeyboard* bad_usb_keyboard_v1_alloc_read(File* layout) { | ||
struct Layout* data = malloc(sizeof(*data)); | ||
if(storage_file_read(layout, data, sizeof(*data)) != sizeof(*data)) { | ||
free(data); | ||
return NULL; | ||
} | ||
|
||
return bad_usb_keyboard_alloc(data, &imp_v1); | ||
} |
Oops, something went wrong.