mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-11-04 15:38:24 +00:00
236 lines
8.1 KiB
C++
236 lines
8.1 KiB
C++
#include "CardputerLayout.h"
|
|
#include <new>
|
|
#include <string.h>
|
|
|
|
using namespace Keyboard::Layout;
|
|
|
|
// The built-in map is immutable; keep its 450 bytes in flash instead of DRAM.
|
|
static const LayoutLayer default_layout_layers[2] = {
|
|
{ (Keyboard::Layout::LAYER_MASK_BASE),
|
|
{ "`", "", "", "", "1", "q", "", "", "2", "w", "a", "", "3", "e", "s", "z", "4", "r", "d",
|
|
"x", "5", "t", "f", "c", "6", "y", "g", "v", "7", "u", "h", "b", "8", "i", "j", "n", "9", "o",
|
|
"k", "m", "0", "p", "l", ",", "-", "[", ";", ".", "=", "]", "'", "/", "", "\\", "", "" } },
|
|
{ (Keyboard::Layout::LAYER_MASK_SHIFT),
|
|
{ "~", "", "", "", "!", "Q", "", "", "@", "W", "A", "", "#", "E", "S", "Z", "$", "R", "D",
|
|
"X", "%", "T", "F", "C", "^", "Y", "G", "V", "&", "U", "H", "B", "*", "I", "J", "N", "(", "O",
|
|
"K", "M", ")", "P", "L", "<", "_", "{", ":", ">", "+", "}", "\"", "?", "", "|", "", "" } }
|
|
};
|
|
|
|
static LayoutData default_layout = { .language_code = "en",
|
|
.layers_count = 2,
|
|
.layers = default_layout_layers };
|
|
|
|
uint8_t CardputerLayout::getLayerMask(Keyboard::Modifiers modifiers) const {
|
|
return (modifiers.fn ? LAYER_MASK_FN : 0) | (modifiers.ctrl ? LAYER_MASK_CTRL : 0) |
|
|
(modifiers.shift ? LAYER_MASK_SHIFT : 0) | (modifiers.opt ? LAYER_MASK_OPT : 0) |
|
|
(modifiers.alt ? LAYER_MASK_ALT : 0);
|
|
}
|
|
|
|
Keyboard::Layout::CardputerLayout::CardputerLayout()
|
|
: main_layout(&default_layout), current_layout(&default_layout) {}
|
|
|
|
CardputerLayout::~CardputerLayout() {
|
|
if (main_layout != &default_layout) {
|
|
delete[] main_layout->layers;
|
|
delete main_layout;
|
|
}
|
|
if (alternate_layout) {
|
|
delete[] alternate_layout->layers;
|
|
delete alternate_layout;
|
|
}
|
|
}
|
|
|
|
|
|
const char *CardputerLayout::lookup(Keyboard::Event &event, bool force_default) {
|
|
if (event.key == Keyboard::KEY_NONE || event.key > TOTAL_KEYS || !current_layout) {
|
|
return "";
|
|
}
|
|
|
|
const LayoutData *tgt = force_default ? &default_layout : current_layout;
|
|
uint8_t mask = getLayerMask(event.modifiers);
|
|
uint8_t key = event.key - 1;
|
|
|
|
// Try to find an exact match in the current modifier layer
|
|
for (uint8_t i = 0; i < tgt->layers_count; i++) {
|
|
if (mask == tgt->layers[i].modifiers_mask) {
|
|
const char *data = tgt->layers[i].entries[key].data;
|
|
if (data && data[0]) {
|
|
return data;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If Shift is pressed and nothing was found, try the dedicated SHIFT layer
|
|
if (event.modifiers.shift && mask != LAYER_MASK_SHIFT) {
|
|
for (uint8_t i = 0; i < tgt->layers_count; i++) {
|
|
if (LAYER_MASK_SHIFT == tgt->layers[i].modifiers_mask) {
|
|
const char *data = tgt->layers[i].entries[key].data;
|
|
if (data && data[0]) {
|
|
return data;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback to the BASE layer if we haven't returned anything yet
|
|
if (mask != LAYER_MASK_BASE) {
|
|
for (uint8_t i = 0; i < tgt->layers_count; i++) {
|
|
if (LAYER_MASK_BASE == tgt->layers[i].modifiers_mask) {
|
|
const char *data = tgt->layers[i].entries[key].data;
|
|
if (data && data[0]) {
|
|
return data;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle space if it not overriden
|
|
if (event.key == Keyboard::KEY_SPACE && !event.modifiers.ctrl) {
|
|
return " ";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
void CardputerLayout::begin(FS &fs) {
|
|
parseLayout(fs, LAYOUT_TYPE_MAIN);
|
|
parseLayout(fs, LAYOUT_TYPE_ALT);
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Read only the declared, bounded file bytes. A device which returns EOF or
|
|
// repeats bytes without advancing must not spin or silently publish a prefix.
|
|
bool readLayoutLine(File& file, size_t& remaining, char* line) {
|
|
size_t length = 0;
|
|
while (remaining) {
|
|
const size_t position = file.position();
|
|
const int value = file.read();
|
|
if (value < 0 || file.position() != position + 1) return false;
|
|
--remaining;
|
|
if (value == '\n') break;
|
|
if (value == 0 || length == CardputerLayout::MAX_LAYOUT_LINE_BYTES) return false;
|
|
line[length++] = static_cast<char>(value);
|
|
}
|
|
if (length && line[length - 1] == '\r') --length;
|
|
line[length] = '\0';
|
|
return true;
|
|
}
|
|
|
|
bool readLayerMask(const char* line, uint8_t& mask) {
|
|
if (line[0] != 'L' || strlen(line) < 3) return false;
|
|
mask = 0;
|
|
for (size_t i = 2; line[i]; ++i) {
|
|
switch (line[i]) {
|
|
case 'B': mask = LAYER_MASK_BASE; break;
|
|
case 'F': mask |= LAYER_MASK_FN; break;
|
|
case 'C': mask |= LAYER_MASK_CTRL; break;
|
|
case 'S': mask |= LAYER_MASK_SHIFT; break;
|
|
case 'O': mask |= LAYER_MASK_OPT; break;
|
|
case 'A': mask |= LAYER_MASK_ALT; break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void CardputerLayout::parseLayout(FS &fs, LayoutType layoutType) {
|
|
const char *filename = (layoutType == LAYOUT_TYPE_MAIN) ? KB_LAYOUT_MAIN_FILE : KB_LAYOUT_ALT_FILE;
|
|
if (!fs.exists(filename)) {
|
|
return;
|
|
}
|
|
|
|
File layout_file = fs.open(filename, FILE_READ, false);
|
|
if (!layout_file) {
|
|
return;
|
|
}
|
|
|
|
const size_t file_bytes = layout_file.size();
|
|
if (file_bytes == 0 || file_bytes > MAX_LAYOUT_FILE_BYTES) {
|
|
layout_file.close();
|
|
return;
|
|
}
|
|
char line[MAX_LAYOUT_LINE_BYTES + 1];
|
|
uint32_t masks = 0;
|
|
uint8_t layer_count = 0;
|
|
size_t remaining = file_bytes;
|
|
bool valid = true;
|
|
while (remaining && valid) {
|
|
valid = readLayoutLine(layout_file, remaining, line);
|
|
uint8_t mask;
|
|
if (valid && readLayerMask(line, mask) && !(masks & (uint32_t(1) << mask))) {
|
|
if (layer_count == MAX_LAYOUT_LAYERS) valid = false;
|
|
else { masks |= uint32_t(1) << mask; ++layer_count; }
|
|
}
|
|
}
|
|
if (!valid || !layer_count || !layout_file.seek(0)) {
|
|
layout_file.close();
|
|
return;
|
|
}
|
|
|
|
LayoutData* layout = new (std::nothrow) LayoutData();
|
|
if (!layout) { layout_file.close(); return; }
|
|
LayoutLayer* layers = new (std::nothrow) LayoutLayer[layer_count]();
|
|
if (!layers) { delete layout; layout_file.close(); return; }
|
|
layout->layers = layers;
|
|
layout->layers_count = 0;
|
|
int current_layer_index = -1;
|
|
remaining = file_bytes;
|
|
while (remaining && valid) {
|
|
valid = readLayoutLine(layout_file, remaining, line);
|
|
if (!valid) break;
|
|
const size_t length = strlen(line);
|
|
uint8_t mask;
|
|
if (line[0] == 'N' && length >= 4) {
|
|
memcpy(layout->language_code, line + 2, 2);
|
|
layout->language_code[2] = '\0';
|
|
} else if (readLayerMask(line, mask)) {
|
|
current_layer_index = 0;
|
|
while (current_layer_index < layout->layers_count
|
|
&& layers[current_layer_index].modifiers_mask != mask) ++current_layer_index;
|
|
if (current_layer_index == layout->layers_count) {
|
|
// The file may change or fail between passes. Never grow past the
|
|
// counted allocation or publish a partial replacement.
|
|
if (layout->layers_count == layer_count) { valid = false; break; }
|
|
layers[current_layer_index].modifiers_mask = mask;
|
|
++layout->layers_count;
|
|
}
|
|
} else if (line[0] == 'K' && length >= 5 && line[4] == '='
|
|
&& current_layer_index >= 0) {
|
|
const int key_id = (line[2] >= '0' && line[2] <= '9'
|
|
&& line[3] >= '0' && line[3] <= '9')
|
|
? (line[2] - '0') * 10 + line[3] - '0' : 0;
|
|
if (key_id >= 1 && key_id <= TOTAL_KEYS) {
|
|
char* dest = layers[current_layer_index].entries[key_id - 1].data;
|
|
memset(dest, 0, KB_LAYOUT_CHAR_MAX + 1);
|
|
memcpy(dest, line + 5, length - 5 < KB_LAYOUT_CHAR_MAX ? length - 5 : KB_LAYOUT_CHAR_MAX);
|
|
}
|
|
}
|
|
}
|
|
layout_file.close();
|
|
if (!valid || layout->layers_count != layer_count) {
|
|
delete[] layout->layers;
|
|
delete layout;
|
|
return;
|
|
}
|
|
|
|
LayoutData*& previous = layoutType == LAYOUT_TYPE_MAIN ? main_layout : alternate_layout;
|
|
if (previous && previous != &default_layout) {
|
|
delete[] previous->layers;
|
|
delete previous;
|
|
}
|
|
previous = layout;
|
|
if (current_layout_type == layoutType) current_layout = layout;
|
|
}
|
|
|
|
void Keyboard::Layout::CardputerLayout::switchLayout() {
|
|
LayoutType new_type = (current_layout_type == LAYOUT_TYPE_MAIN) ? LAYOUT_TYPE_ALT : LAYOUT_TYPE_MAIN;
|
|
LayoutData *new_layout = (new_type == LAYOUT_TYPE_MAIN) ? main_layout : alternate_layout;
|
|
|
|
if (new_layout) {
|
|
current_layout_type = new_type;
|
|
current_layout = new_layout;
|
|
}
|
|
};
|