smol_gkos/smol_gkos.ino

547 lines
18 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <Wire.h>
#define TRACKBALL_ADDR 0x0A
#define TRACKBALL_REG_LEFT 0x04
const byte PIN_A = 33;
const byte PIN_B = 32;
const byte PIN_C = 31;
const byte PIN_D = 24;
const byte PIN_E = 25;
const byte PIN_F = 26;
const byte PIN_MOUSE_UPPER_LEFT = 27;
const byte PIN_MOUSE_UPPER_RIGHT = 30;
const byte PIN_MOUSE_LOWER_LEFT = 28;
const byte PIN_MOUSE_LOWER_RIGHT = 29;
const byte PIN_LED1 = 6;
const byte PIN_LED2 = 1;
const byte PIN_LED3 = 3;
// unused here, but the trackball is wired to i2c bus 0 with these pins
// const byte PIN_TRACKBALL_SDA = 18;
// const byte PIN_TRACKBALL_SCL = 19;
// custom masks for forcing modifiers for keypresses
// teensy's inbuilt ones (eg. SHIFT_MASK) conflict with arrow keys etc.
#define MASK_SHIFT 0x100
#define MASK_ALTGR 0x200
#define MASK_CTRL 0x800
enum Button { A, B, C, D, E, F, MouseUL, MouseUR, MouseDL, MouseDR };
const int button_pins[10] = {
PIN_A, PIN_B, PIN_C, PIN_D, PIN_E, PIN_F,
PIN_MOUSE_UPPER_LEFT, PIN_MOUSE_UPPER_RIGHT,
PIN_MOUSE_LOWER_LEFT, PIN_MOUSE_LOWER_RIGHT
};
// how many update cycles a button has to stay pressed before
// it counts as being pressed
const int debounce_time = 50;
int button_timers[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
// different actions the keyboard can take
// - press (and release) a key
// - toggle a modifier
enum ActionType { PressKey, ToggleModifier };
/*************************************************
KEYBOARD KEYS (ON THEIR OWN)
*************************************************/
const int keys[6] = { KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F };
const int keys_symbol[6] = {
KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6
};
const int keys_function[6] = {
KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6
};
/*************************************************
CHORDS AND KEYS THAT CAN BE CHORDED INTO
*************************************************/
/*
defined as a 6-bit number with the physical buttons corresponding to
each of the bits (A = 1, B = 2, C = 4, D = 8, E = 16, F = 32)
backspace, space (keep those two first since they overlap with the rest)
g, k, o, s, w, native
*/
const byte chords[8] = { 7, 56, 24, 48, 3, 6, 40, 5 };
// buttons that can be combined with a chord for a keypress
const byte chord_buttons[24] = {
Button::D, Button::E, Button::F,
Button::A, Button::B, Button::C,
Button::A, Button::B, Button::C,
Button::A, Button::B, Button::C,
Button::D, Button::E, Button::F,
Button::D, Button::E, Button::F,
Button::A, Button::B, Button::C,
Button::D, Button::E, Button::F,
};
// keypresses for chords in sets of 4
// index 0 is the chord on its own, 1-3 correspond to chord_buttons
const int chord_targets[32] = {
KEY_BACKSPACE, KEY_LEFT + MASK_CTRL, KEY_LEFT, KEY_HOME,
KEY_SPACE, KEY_RIGHT + MASK_CTRL, KEY_RIGHT, KEY_END,
KEY_G, KEY_H, KEY_I, KEY_J,
KEY_K, KEY_L, KEY_M, KEY_N,
KEY_O, KEY_P, KEY_Q, KEY_R,
KEY_S, KEY_T, KEY_U, KEY_V,
KEY_W, KEY_X, KEY_Y, KEY_Z,
0, 0, 0, 0
};
const int chord_targets_symbol[32] = {
0, 0, 0, 0,
0, 0, 0, 0,
KEY_0, KEY_7, KEY_8, KEY_9,
KEY_BACKSLASH, KEY_QUOTE + MASK_SHIFT, // # @
KEY_5 + MASK_ALTGR, KEY_7 + MASK_SHIFT, // ½ &
KEY_EQUAL + MASK_SHIFT, KEY_5 + MASK_SHIFT, // + %
KEY_EQUAL, KEY_6 + MASK_SHIFT, // = ^
KEY_8 + MASK_SHIFT, KEY_4 + MASK_SHIFT, // * $
KEY_4 + MASK_ALTGR, KEY_3 + MASK_SHIFT, // € £
KEY_9 + MASK_SHIFT, KEY_LEFT_BRACE, // ( [
KEY_COMMA + MASK_SHIFT, KEY_LEFT_BRACE + MASK_SHIFT, // < {
KEY_0 + MASK_SHIFT, KEY_RIGHT_BRACE, // ) ]
KEY_PERIOD + MASK_SHIFT, KEY_RIGHT_BRACE + MASK_SHIFT, // > }
};
const int chord_targets_function[32] = {
KEY_MEDIA_PLAY_PAUSE, 0, KEY_MEDIA_PREV_TRACK, 0,
KEY_MEDIA_MUTE, 0, KEY_MEDIA_NEXT_TRACK, 0,
KEY_F10, KEY_F7, KEY_F8, KEY_F9,
0, KEY_F11, KEY_F12, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0
};
/*************************************************
SPECIAL KEY COMBINATIONS
*************************************************/
/*
dash, backslash, slash, apostrophe, comma, exclamation point, question mark,
period, up, down, page up, page down, shift, symbol shift, switch keyset,
escape, control, alt, delete, function, gui, tab, enter
*/
const byte specials[23] = {
17, 51, 30, 10, 20, 12, 33, 34, 9, 36, 27,
54, 18, 45, 63, 31, 47, 55, 62, 29, 43, 61, 59
};
enum Modifier { Shift, SymbolShift, Keyset, Control, Alt, Gui, Function };
const int special_action_targets[23] = {
KEY_MINUS, KEY_MINUS + MASK_ALTGR, // - \.
KEY_SLASH, KEY_QUOTE, // / '
KEY_COMMA, KEY_1 + MASK_SHIFT, // , !
KEY_SLASH + MASK_SHIFT, KEY_PERIOD, // ! ?
KEY_UP, KEY_DOWN, KEY_PAGE_UP, KEY_PAGE_DOWN,
Modifier::Shift, Modifier::SymbolShift, Modifier::Keyset,
KEY_ESC, Modifier::Control, Modifier::Alt, KEY_DELETE,
Modifier::Function, Modifier::Gui, KEY_TAB, KEY_ENTER
};
const int special_action_targets_symbol[23] = {
KEY_MINUS + MASK_SHIFT, KEY_TILDE, // _ `
KEY_SEMICOLON + MASK_ALTGR, KEY_2 + MASK_SHIFT, // ´ "
KEY_SEMICOLON, KEY_TILDE + MASK_ALTGR, // ; |
KEY_BACKSLASH + MASK_SHIFT, KEY_SEMICOLON + MASK_SHIFT, // ~ :
0, 0, 0, 0,
0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0
};
const int special_action_targets_function[23] = {
0, 0, 0, 0,
0, 0, 0, 0,
KEY_MEDIA_VOLUME_INC, KEY_MEDIA_VOLUME_DEC, 0, 0,
0, 0, 0,
0, 0, 0, KEY_PRINTSCREEN,
0, 0, 0, 0
};
const byte special_action_target_types[23] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0
};
// bitwise variables, see line 63
byte key_pressed = 0;
byte key_pressed_total = 0;
bool chord_used = false;
bool chorded_pressed = false;
// modifiers that can be toggled
// these get unset after each keypress (save for the locks)
bool mod_shift = false;
bool mod_shift_lock = false;
bool mod_symbol = false;
bool mod_symbol_lock = false;
bool mod_control = false;
bool mod_alt = false;
bool mod_altgr = false;
bool mod_gui = false;
bool mod_function = false;
IntervalTimer trackball_timer;
volatile bool trackball_update = false;
byte mouse_up = 0;
byte mouse_down = 0;
byte mouse_left = 0;
byte mouse_right = 0;
bool mouse_button = false;
/*
we do a rudimentary form of mouse acceleration by adjusting movement speed
based on the distance moved during the last [num_mouse_samples] updates
*/
const int num_mouse_samples = 10;
int current_mouse_sample = 0;
int mouse_movement_sample = 0;
float mouse_acceleration = 0.0f;
const float mouse_accel_factor = 0.7f;
const int mouse_scroll_sensitivity = 6;
int mouse_scroll_dist = 0;
bool mouse_middle_pressed = false;
bool mouse_scrolled = false;
void setup() {
pinMode(PIN_A, INPUT_PULLUP);
pinMode(PIN_B, INPUT_PULLUP);
pinMode(PIN_C, INPUT_PULLUP);
pinMode(PIN_D, INPUT_PULLUP);
pinMode(PIN_E, INPUT_PULLUP);
pinMode(PIN_F, INPUT_PULLUP);
pinMode(PIN_MOUSE_UPPER_LEFT, INPUT_PULLUP);
pinMode(PIN_MOUSE_UPPER_RIGHT, INPUT_PULLUP);
pinMode(PIN_MOUSE_LOWER_LEFT, INPUT_PULLUP);
pinMode(PIN_MOUSE_LOWER_RIGHT, INPUT_PULLUP);
pinMode(PIN_LED1, OUTPUT);
pinMode(PIN_LED2, OUTPUT);
pinMode(PIN_LED3, OUTPUT);
// poll trackball at 10kHz
trackball_timer.begin(trackball_isr, 100);
Wire.begin();
}
void loop() {
/*******************
BUTTONS
*******************/
for (byte b = 0; b < 10; b++) {
// button is pressed
if (!digitalRead(button_pins[b])) {
// increment button timer if it's not full
if (button_timers[b] < debounce_time)
button_timers[b]++;
else {
// update pressed gkos keys
if (b < 6) {
key_pressed |= 1 << b;
key_pressed_total |= 1 << b;
}
// update pressed mouse keys
else {
switch (b) {
case Button::MouseUL:
Mouse.press(MOUSE_LEFT);
break;
case Button::MouseUR:
Mouse.press(MOUSE_RIGHT);
break;
case Button::MouseDL:
case Button::MouseDR:
mouse_middle_pressed = true;
break;
}
}
}
}
// button is released
else {
// if the button was debounced, it counts as a press
if (button_timers[b] >= debounce_time) {
// gkos key released
if (b < 6) {
key_pressed &= ~(1 << b);
key_released(b);
update_leds();
if (key_pressed == 0) {
key_pressed_total = 0;
// clear flags
chord_used = false;
chorded_pressed = false;
}
}
// mouse button released
else {
switch (b) {
case Button::MouseUL:
Mouse.release(MOUSE_LEFT);
break;
case Button::MouseUR:
Mouse.release(MOUSE_RIGHT);
break;
case Button::MouseDL:
case Button::MouseDR:
if (!mouse_scrolled)
Mouse.click(MOUSE_MIDDLE);
mouse_middle_pressed = mouse_scrolled = false;
mouse_scroll_dist = 0;
break;
}
}
}
button_timers[b] = 0;
}
}
/*******************
TRACKBALL
*******************/
noInterrupts();
if (trackball_update) {
Wire.beginTransmission(TRACKBALL_ADDR);
Wire.write(TRACKBALL_REG_LEFT);
Wire.endTransmission();
Wire.requestFrom(TRACKBALL_ADDR, 5);
mouse_left = Wire.read();
mouse_right = Wire.read();
mouse_up = Wire.read();
mouse_down = Wire.read();
mouse_button = Wire.read();
int mouse_x = mouse_right - mouse_left;
int mouse_y = mouse_down - mouse_up;
// calculate mouse acceleration every [num_mouse_samples] updates
mouse_movement_sample += abs(mouse_x) + abs(mouse_y);
current_mouse_sample++;
if (current_mouse_sample > num_mouse_samples) {
current_mouse_sample = 0;
mouse_acceleration = 1.0f + mouse_movement_sample * mouse_accel_factor;
mouse_movement_sample = 0;
}
// scroll with the middle mouse button
if (mouse_middle_pressed) {
mouse_scroll_dist += mouse_y;
if (abs(mouse_scroll_dist) > mouse_scroll_sensitivity) {
Mouse.scroll(mouse_scroll_dist % mouse_scroll_sensitivity);
mouse_scroll_dist %= mouse_scroll_sensitivity;
mouse_scrolled = true;
}
} else {
Mouse.move(mouse_x * mouse_acceleration, mouse_y * mouse_acceleration);
}
trackball_update = false;
}
interrupts();
}
void key_released(byte key) {
int target = 0;
/*************************************************
SPECIAL KEY COMBINATIONS
*************************************************/
bool is_special = false;
byte special = 0;
for (byte b = 0; b < 23; b++) {
if (key_pressed_total == specials[b]) {
is_special = true;
special = b;
break;
}
}
if (is_special && !chord_used) {
// only register a special keypress when all keys have been released
if (key_pressed != 0)
return;
target = special_action_targets[special];
if ((mod_shift || mod_shift_lock || mod_symbol || mod_symbol_lock) &&
special_action_targets_symbol[special] != 0)
target = special_action_targets_symbol[special];
if (mod_function &&
special_action_targets_function[special] != 0)
target = special_action_targets_function[special];
switch (special_action_target_types[special]) {
case ActionType::PressKey:
press_key(target);
return;
case ActionType::ToggleModifier:
switch (target) {
case Modifier::Shift:
mod_symbol = mod_symbol_lock = false;
if (mod_shift_lock) {
mod_shift_lock = false;
} else if (mod_shift) {
mod_shift = false;
mod_shift_lock = true;
} else {
mod_shift = true;
}
return;
case Modifier::SymbolShift:
mod_shift = mod_shift_lock = mod_symbol_lock = false;
mod_symbol = !mod_symbol;
return;
case Modifier::Keyset:
mod_shift = mod_shift_lock = mod_symbol = false;
mod_symbol_lock = !mod_symbol_lock;
return;
case Modifier::Control:
mod_control = !mod_control;
return;
case Modifier::Alt:
mod_alt = !mod_alt;
return;
case Modifier::Gui:
mod_gui = !mod_gui;
return;
case Modifier::Function:
mod_function = !mod_function;
return;
}
}
return;
}
/*************************************************
CHORDS AND KEYS THAT CAN BE CHORDED INTO
*************************************************/
bool is_chord = false;
byte chord = 0;
for (byte b = 0; b < 8; b++) {
if ((key_pressed_total & chords[b]) == chords[b]) {
is_chord = true;
chord = b;
chord_used = true;
break;
}
}
if (is_chord) {
// chord on its own
if (!chorded_pressed && key_pressed == 0 &&
key_pressed_total == chords[chord]) {
target = chord_targets[chord * 4];
if ((mod_symbol || mod_symbol_lock) &&
chord_targets_symbol[chord * 4] != 0)
target = chord_targets_symbol[chord * 4];
if (mod_function &&
chord_targets_function[chord * 4] != 0)
target = chord_targets_function[chord * 4];
press_key(target);
return;
}
// keys that can be chorded with
for (byte b = 0; b < 3; b++) {
if (key == chord_buttons[chord * 3 + b]) {
target = chord_targets[chord * 4 + 1 + b];
if ((mod_symbol || mod_symbol_lock) &&
chord_targets_symbol[chord * 4 + 1 + b] != 0)
target = chord_targets_symbol[chord * 4 + 1 + b];
if (mod_function &&
chord_targets_function[chord * 4 + 1 + b] != 0)
target = chord_targets_function[chord * 4 + 1 + b];
// erase key from total so you can hold the chord down for
// multiple chorded keypresses
key_pressed_total &= ~(1 << key);
chorded_pressed = true;
press_key(target);
return;
}
}
return;
}
/*************************************************
KEYBOARD KEYS (ON THEIR OWN)
*************************************************/
if (mod_function && keys_function[key] != 0)
press_key(keys_function[key]);
else if (mod_symbol || mod_symbol_lock)
press_key(keys_symbol[key]);
else
press_key(keys[key]);
return;
}
void press_key(int key) {
// check if modifiers need to be forced
if (key & MASK_SHIFT) {
mod_shift = true;
key &= ~(MASK_SHIFT);
}
if (key & MASK_ALTGR) {
mod_altgr = true;
key &= ~(MASK_ALTGR);
}
if (key & MASK_CTRL) {
mod_control = true;
key &= ~(MASK_CTRL);
}
// press modifiers
if (mod_shift || mod_shift_lock)
Keyboard.press(KEY_LEFT_SHIFT);
if (mod_control)
Keyboard.press(KEY_LEFT_CTRL);
if (mod_alt)
Keyboard.press(KEY_LEFT_ALT);
if (mod_altgr)
Keyboard.press(KEY_RIGHT_ALT);
if (mod_gui)
Keyboard.press(KEY_LEFT_GUI);
// keypress
Keyboard.press(key);
// small delay since slower computers can drop quick inputs
// this is very much a hack, and probably messes with the debouncing etc.
// but pending a debouncing rewrite to a timer-based system this should
// work in the meanwhile provided you don't type too quickly
delay(50);
Keyboard.release(key);
// release modifiers
if (mod_shift || mod_shift_lock)
Keyboard.release(KEY_LEFT_SHIFT);
if (mod_control)
Keyboard.release(KEY_LEFT_CTRL);
if (mod_alt)
Keyboard.release(KEY_LEFT_ALT);
if (mod_altgr)
Keyboard.release(KEY_RIGHT_ALT);
if (mod_gui)
Keyboard.release(KEY_LEFT_GUI);
// clear temporary modifiers
mod_shift = mod_symbol = mod_control = mod_alt = mod_altgr =
mod_gui = mod_function = false;
}
void update_leds() {
digitalWrite(PIN_LED1, mod_shift || mod_shift_lock);
digitalWrite(PIN_LED2, mod_symbol || mod_symbol_lock);
digitalWrite(PIN_LED3, mod_control || mod_alt || mod_gui || mod_function);
}
int sign(int num) {
if (num >= 0) return 1;
return -1;
}
void trackball_isr() {
trackball_update = true;
}