keymapper/keymapper.ino

402 行
11 KiB
C++

/*
Usage: Convert standard QWERTY keyboard to any keyboard layout you want
Default layout: QWERTY
Reserved key combination to switch layout:
Ctrl-Shift 0 => QWERTY (DEFAULT)
Ctrl-Shift 1 => tarmak1
Ctrl-Shift 2 => tarmak2
Ctrl-Shift 3 => tarmak3
Ctrl-Shift 4 => tarmak4
Ctrl-Shift 5 => Colemak
Ctrl-Shift 6 => Dvorak
Ctrl-Shift 7 => Workman
*/
#ifdef __MK66FX1M0__
/* Teensy 3.6 */
#define TEENSY_USB_HOST 1
#endif
#include <avr/pgmspace.h>
#ifdef TEENSY_USB_HOST
#include <USBHost_t36.h>
#else
#include <Usb.h>
#include <hidboot.h>
#endif
#include <Keyboard.h>
#include "keymapper_game.h"
//#define DEBUG
#define modeLED LED_BUILTIN
// function definitions
bool HandleReservedKeystrokes(uint8_t *buf);
inline void SendKeysToHost (uint8_t *buf);
void play_word_game(void);
inline void LatchKey (uint8_t keyToLatch);
// variable definitions
typedef enum
{
qwerty = 0,
tarmak1,
tarmak2,
tarmak3,
tarmak4,
colemak,
dvorak,
workman
} KeyboardLayout;
// Keymap based on the scancodes from 4 to 57, refer to the HID usage table on the meaning of each element
PROGMEM const uint8_t qwertyKeymap[] = {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};
PROGMEM const uint8_t tarmak1Keymap[] = {4, 5, 6, 7, 13, 9, 10, 11, 12, 17, 8, 15, 16, 14, 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, 42};
PROGMEM const uint8_t tarmak2Keymap[] = {4, 5, 6, 7, 9, 23, 13, 11, 12, 17, 8, 15, 16, 14, 18, 19, 20, 21, 22, 10, 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, 42};
PROGMEM const uint8_t tarmak3Keymap[] = {4, 5, 6, 7, 9, 23, 51, 11, 12, 17, 8, 15, 16, 14, 28, 19, 20, 21, 22, 10, 24, 25, 26, 27, 13, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 18, 52, 53, 54, 55, 56, 42};
PROGMEM const uint8_t tarmak4Keymap[] = {4, 5, 6, 7, 9, 23, 51, 11, 24, 17, 8, 12, 16, 14, 28, 19, 20, 21, 22, 10, 15, 25, 26, 27, 13, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 18, 52, 53, 54, 55, 56, 42};
PROGMEM const uint8_t colemakKeymap[] = {4, 5, 6, 22, 9, 23, 7, 11, 24, 17, 8, 12, 16, 14, 28, 51, 20, 19, 21, 10, 15, 25, 26, 27, 13, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 18, 52, 53, 54, 55, 56, 42};
PROGMEM const uint8_t dvorakKeymap[] = {4, 27, 13, 8, 55, 24, 12, 7, 6, 11, 23, 17, 16, 5, 21, 15, 52, 19, 18, 28, 10, 14, 54, 20, 9, 51, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 47, 48, 56, 46, 49, 50, 22, 45, 53, 26, 25, 29, 57};
PROGMEM const uint8_t workmanKeymap[] = {4, 25, 16, 11, 21, 23, 10, 28, 24, 17, 8, 18, 15, 14, 19, 51, 20, 26, 22, 5, 9, 6, 7, 27, 13, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 12, 52, 53, 54, 55, 56, 42};
const uint8_t *Keymap[] =
{
qwertyKeymap,
tarmak1Keymap,
tarmak2Keymap,
tarmak3Keymap,
tarmak4Keymap,
colemakKeymap,
dvorakKeymap,
workmanKeymap
};
// global variables
//uint32_t ledBlinkTime = millis();
//uint16_t ledBlinkDelay = 500;
KeyboardLayout CurrentLayout = qwerty;
uint8_t KeyBuffer[8] = {0, 0, 0, 0, 0, 0, 0, 0};
uint8_t specialKeyLatch = 0;
bool specialKeyLatchReleased = false;
void print_hex(const uint8_t *buf, size_t len)
{
#ifdef DEBUG
for (size_t i = 0; i < 8; i++) {
if (i) Serial.print(' ');
Serial.print(buf[i], HEX);
}
Serial.println();
#endif
}
// *******************************************************************************************
// Parse
// *******************************************************************************************
void remapper(uint8_t *buf, uint8_t len)
{
uint8_t i;
// On error - return
if (buf[2] == 1)
return;
print_hex(buf, len);
KeyBuffer[0] = buf[0];
if (!HandleReservedKeystrokes(buf))
{
specialKeyLatchReleased = true;
// remap all keys according to the existing keymap
for (i = 2; i < 8; i++)
{
// handle special case of Shift-CAPSLOCK to be ignored by the remapper
if (buf[i] == KEY_CAPS_LOCK && buf[0] & 0x22)
{
KeyBuffer[i] = KEY_CAPS_LOCK;
LatchKey(KEY_CAPS_LOCK);
}
else
{
// print the key based on the current layout
if (buf[i] >= 4 && buf[i] <= 57) // transpose of 4 becoz our array starts from 0 but A is 4
// limit check to 57, which is the last mappable key (CAPSLOCK)
{
// if it was a special key of shift-CAPS, then only allow mapping if the key has been released at least once
if (buf[i] != specialKeyLatch)
KeyBuffer[i] = pgm_read_byte(Keymap[CurrentLayout] + buf[i] - 4);
else // key is not released yet. do not allow mapping
{
// Serial.println("key is not released");
KeyBuffer[i] = 0;
specialKeyLatchReleased = false;
}
}
else
KeyBuffer[i] = buf[i];
}
}
// reset latch if key is released
if (specialKeyLatchReleased)
{
// Serial.println("latch is released");
specialKeyLatch = 0;
}
// send out key press
SendKeysToHost (KeyBuffer);
print_hex(KeyBuffer, 8);
}
}
bool HandleReservedKeystrokes(uint8_t *buf) // return true if it is a reserved keystroke
{
uint8_t mod = buf[0]; // read the modifier byte
uint8_t numKeysPressed = 0;
uint8_t keyPosition = 0;
// check that there is only 1 single key that is pressed
for (uint8_t i = 2; i < 8; i++) if (buf[i] > 0) {
numKeysPressed++;
keyPosition = i;
}
if (numKeysPressed != 1) return false; // only allow single keypress for reserved keystrokes (besides modifiers)
// check if we are changing layouts
if ((mod & 0x22) && (mod & 0x11)) { // Shift-Alt keystrokes
switch (buf[keyPosition]) {
case 0x27: // 0
CurrentLayout = qwerty;
digitalWrite(modeLED, LOW);
LatchKey(buf[keyPosition]);
return true;
case 0x1e: // 1
CurrentLayout = tarmak1;
digitalWrite(modeLED, HIGH);
LatchKey(buf[keyPosition]);
return true;
case 0x1f: // 2
CurrentLayout = tarmak2;
digitalWrite(modeLED, HIGH);
LatchKey(buf[keyPosition]);
return true;
case 0x20: // 3
CurrentLayout = tarmak3;
digitalWrite(modeLED, HIGH);
LatchKey(buf[keyPosition]);
return true;
case 0x21: // 4
CurrentLayout = tarmak4;
digitalWrite(modeLED, HIGH);
LatchKey(buf[keyPosition]);
return true;
case 0x22: // 5
CurrentLayout = colemak;
digitalWrite(modeLED, HIGH);
LatchKey(buf[keyPosition]);
return true;
case 0x23: // 6
CurrentLayout = dvorak;
digitalWrite(modeLED, HIGH);
LatchKey(buf[keyPosition]);
return true;
case 0x24: // 7
CurrentLayout = workman;
digitalWrite(modeLED, HIGH);
LatchKey(buf[keyPosition]);
return true;
case 0x2c: // space bar
play_word_game();
LatchKey(buf[keyPosition]);
return true;
}
}
return false;
}
inline void SendKeysToHost (uint8_t *buf)
{
#ifdef TEENSYDUINO
Keyboard.set_modifier(buf[0]);
Keyboard.set_key1(buf[2]);
Keyboard.set_key2(buf[3]);
Keyboard.set_key3(buf[4]);
Keyboard.set_key4(buf[5]);
Keyboard.set_key5(buf[6]);
Keyboard.set_key6(buf[7]);
Keyboard.send_now();
#else /* TEENSYDUINO */
HID().SendReport(2, buf, 8);
#endif /* TEENSYDUINO */
}
inline void LatchKey (uint8_t keyToLatch)
{
specialKeyLatch = keyToLatch;
specialKeyLatchReleased = false;
// Serial.print(keyToLatch);
// Serial.println(" is latched");
}
// *******************************************************************************************
// WORD GAME!!!
// *******************************************************************************************
void play_word_game(void)
{
char buffer[GAME_MAXWORDLENGTH];
char priorityAlphabets[10];
char *instrPtr;
uint16_t randNum;
switch (CurrentLayout) {
case tarmak1:
strcpy (priorityAlphabets, "nek");
break;
case tarmak2:
strcpy (priorityAlphabets, "ftg");
break;
case tarmak3:
strcpy (priorityAlphabets, "jyo");
break;
case tarmak4:
strcpy (priorityAlphabets, "lui");
break;
case colemak:
strcpy (priorityAlphabets, "rspd");
break;
default:
strcpy (priorityAlphabets, "");
}
Keyboard.print( "Word game! Letters being prioritised: " );
Keyboard.println( priorityAlphabets );
for (int i = 0; i < 15; i++) {
if (priorityAlphabets[0] != 0) {
instrPtr = NULL;
while (instrPtr == NULL) {
randNum = random(GAME_NUMWORDS);
strcpy_P(buffer, (char*)pgm_read_word(&(game_word_list[randNum])));
instrPtr = strpbrk (buffer, priorityAlphabets);
}
}
else {
randNum = random(GAME_NUMWORDS);
strcpy_P(buffer, (char*)pgm_read_word(&(game_word_list[randNum])));
}
Keyboard.print( buffer );
Keyboard.print( " " );
}
Keyboard.println( "" );
}
#ifdef TEENSY_USB_HOST
USBHost Usb;
KeyboardController keyboard_in(Usb);
/*
Key remap possiblities
Swap LeftCtrl and CapsLock. Put CapsLock in the corner where it belongs!
Switchable QWERTY, Dvorak, and colemak keyboard layouts
Hardware key macros
Remappings and macros that work even in BIOS and recovery mode.
Remappings and macros that work with KVMs and game consoles.
*/
void reportReader(uint8_t report[8])
{
remapper(report, 8);
}
#else /* TEENSY_USB_HOST */
class KbdRptParser : public KeyboardReportParser
{
protected:
virtual void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
};
void KbdRptParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf)
{
remapper(buf, len);
// Run parent class method so keyboard LEDs are updated.
KeyboardReportParser::Parse(hid, is_rpt_id, len, buf);
};
USB Usb;
//USBHub Hub(&Usb);
HIDBoot<USB_HID_PROTOCOL_KEYBOARD> ExtKeyboard(&Usb);
KbdRptParser Prs;
#endif /* TEENSY_USB_HOST */
void setup()
{
randomSeed(analogRead(0));
// initialize the digital pin as an output.
pinMode(modeLED, OUTPUT);
Keyboard.begin();
#ifdef DEBUG
Serial.begin( 115200 );
while (!Serial) delay(1);
Serial.println("Start");
#endif
#ifdef TEENSY_USB_HOST
Usb.begin();
keyboard_in.attachReportReader(reportReader);
#else
if (Usb.Init() == -1)
#ifdef DEBUG
Serial.println("USB host did not start.");
#else
delay( 1 );
#endif
delay( 200 );
ExtKeyboard.SetReportParser(0, (HIDReportParser*)&Prs);
#endif
}
void loop()
{
Usb.Task();
}