QMKhost/src/main.cpp

480 lines
8.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include <stdio.h>
#include <cstdint>
#include <vector>
#include <stdexcept>
#include <string>
// For reading FIFO
#include <fcntl.h>
#include <unistd.h>
// For sleep
#include <chrono>
#include <thread>
// MPD client
#include "mpd/client.h"
// Spell checking
#include "hunspell.hxx"
// Local files
#include "utility/bitmap.hpp"
#include "utility/buffer.hpp"
#include "signal_processing/fft.hpp"
#include "ergodox.hpp"
#include "commands.h"
#include "config.h"
#include "spdlog/spdlog.h"
// TODO:
//
// MPD connection error handling
// Cleaner building
//
// FFT:
// stereo support (and maybe different bitrates?)
// Optimization: don't copy filename in buffer?
// understand consumption rate
// understand BIN2HZ
// understand values and sizes (DFT_TOTAL, DFT_NONZERO, etc)
// note that wave and spectrum have different sizes
// clear fft when not in use
//
//
// Keyboard interface:
// Fix segfault
// Clean up reconnect code
// Better log messages, compiled spdlog
//
// Get parameters from keyboard (width, height, etc)
//
// Later:
// beat detection
// waveform animation
// pcm from pulse
const size_t width = 10;
const size_t height = BOTTOM_SKIP + KB_RESOLUTION + TOP_SKIP;
enum hid_keyboard_keypad_usage {
KC_NO = 0x00,
KC_ROLL_OVER,
KC_POST_FAIL,
KC_UNDEFINED,
KC_A,
KC_B,
KC_C,
KC_D,
KC_E,
KC_F,
KC_G,
KC_H,
KC_I,
KC_J,
KC_K,
KC_L,
KC_M, // 0x10
KC_N,
KC_O,
KC_P,
KC_Q,
KC_R,
KC_S,
KC_T,
KC_U,
KC_V,
KC_W,
KC_X,
KC_Y,
KC_Z,
KC_1,
KC_2,
KC_3, // 0x20
KC_4,
KC_5,
KC_6,
KC_7,
KC_8,
KC_9,
KC_0,
KC_ENTER,
KC_ESCAPE,
KC_BACKSPACE,
KC_TAB,
KC_SPACE,
KC_MINUS,
KC_EQUAL,
KC_LEFT_BRACKET,
KC_RIGHT_BRACKET, // 0x30
KC_BACKSLASH,
KC_NONUS_HASH,
KC_SEMICOLON,
KC_QUOTE,
KC_GRAVE,
KC_COMMA,
KC_DOT,
KC_SLASH,
KC_CAPS_LOCK,
KC_F1,
KC_F2,
KC_F3,
KC_F4,
KC_F5,
KC_F6,
KC_F7, // 0x40
KC_F8,
KC_F9,
KC_F10,
KC_F11,
KC_F12,
KC_PRINT_SCREEN,
KC_SCROLL_LOCK,
KC_PAUSE,
KC_INSERT,
KC_HOME,
KC_PAGE_UP,
KC_DELETE,
KC_END,
KC_PAGE_DOWN,
KC_RIGHT,
KC_LEFT, // 0x50
KC_DOWN,
KC_UP,
KC_NUM_LOCK,
KC_KP_SLASH,
KC_KP_ASTERISK,
KC_KP_MINUS,
KC_KP_PLUS,
KC_KP_ENTER,
KC_KP_1,
KC_KP_2,
KC_KP_3,
KC_KP_4,
KC_KP_5,
KC_KP_6,
KC_KP_7,
KC_KP_8, // 0x60
KC_KP_9,
KC_KP_0,
KC_KP_DOT,
KC_NONUS_BACKSLASH,
KC_APPLICATION,
KC_KB_POWER,
KC_KP_EQUAL,
KC_F13,
KC_F14,
KC_F15,
KC_F16,
KC_F17,
KC_F18,
KC_F19,
KC_F20,
KC_F21, // 0x70
KC_F22,
KC_F23,
KC_F24,
KC_EXECUTE,
KC_HELP,
KC_MENU,
KC_SELECT,
KC_STOP,
KC_AGAIN,
KC_UNDO,
KC_CUT,
KC_COPY,
KC_PASTE,
KC_FIND,
KC_KB_MUTE,
KC_KB_VOLUME_UP, // 0x80
KC_KB_VOLUME_DOWN,
KC_LOCKING_CAPS_LOCK,
KC_LOCKING_NUM_LOCK,
KC_LOCKING_SCROLL_LOCK,
KC_KP_COMMA,
KC_KP_EQUAL_AS400,
KC_INTERNATIONAL_1,
KC_INTERNATIONAL_2,
KC_INTERNATIONAL_3,
KC_INTERNATIONAL_4,
KC_INTERNATIONAL_5,
KC_INTERNATIONAL_6,
KC_INTERNATIONAL_7,
KC_INTERNATIONAL_8,
KC_INTERNATIONAL_9,
KC_LANGUAGE_1, // 0x90
KC_LANGUAGE_2,
KC_LANGUAGE_3,
KC_LANGUAGE_4,
KC_LANGUAGE_5,
KC_LANGUAGE_6,
KC_LANGUAGE_7,
KC_LANGUAGE_8,
KC_LANGUAGE_9,
KC_ALTERNATE_ERASE,
KC_SYSTEM_REQUEST,
KC_CANCEL,
KC_CLEAR,
KC_PRIOR,
KC_RETURN,
KC_SEPARATOR,
KC_OUT, // 0xA0
KC_OPER,
KC_CLEAR_AGAIN,
KC_CRSEL,
KC_EXSEL,
};
// Replacement chars for now, since wide strings are hard.
char ru_kc_to_char(uint8_t keycode) {
switch (keycode) {
case KC_GRAVE:
return '~';//L"ё";
case KC_1:
return '1';//L"1";
case KC_2:
return '2';//L"2";
case KC_3:
return '3';//L"3";
case KC_4:
return '4';//L"4";
case KC_5:
return '5';//L"5";
case KC_6:
return '6';//L"6";
case KC_7:
return '7';//L"7";
case KC_8:
return '8';//L"8";
case KC_9:
return '9';//L"9";
case KC_0:
return '0';//L"0";
case KC_Q:
return '^';//L"й";
case KC_W:
return '*';//L"ц";
case KC_E:
return 'y';//L"у";
case KC_R:
return 'k';//L"к";
case KC_T:
return 'e';//L"е";
case KC_Y:
return 'H';//L"н";
case KC_U:
return 'g';//L"г";
case KC_I:
return 'w';//L"ш";
case KC_O:
return 'W';//L"щ";
case KC_P:
return 'z';//L"з";
case KC_LEFT_BRACKET:
return 'x';//L"х";
case KC_RIGHT_BRACKET:
return '!';//L"ъ";
case KC_A:
return 'f';//L"ф";
case KC_S:
return 'i';//L"ы";
case KC_D:
return 'B';//L"в";
case KC_F:
return 'a';//L"а";
case KC_G:
return 'n';//L"п";
case KC_H:
return 'p';//L"р";
case KC_J:
return 'o';//L"о";
case KC_K:
return 'l';//L"л";
case KC_L:
return 'd';//L"д";
case KC_SEMICOLON:
return 'j';//L"ж";
case KC_QUOTE:
return 'E';//L"э";
case KC_Z:
return 'R';//L"я";
case KC_X:
return 'q';//L"ч";
case KC_C:
return 'c';//L"с";
case KC_V:
return 'm';//L"м";
case KC_B:
return 'n';//L"и";
case KC_N:
return 't';//L"т";
case KC_M:
return '=';//L"ь";
case KC_COMMA:
return 'b';//L"б";
case KC_DOT:
return 'u';//L"ю";
default:
spdlog::warn("Unknown keycode passed to ru_kc_to_char");
return '?';//L"?";
}
}
int main(int argc, char *argv[]) {
Hunspell* hun = new Hunspell(HUNSPELL_AFF_EN, HUNSPELL_DIC_EN);
spdlog::set_level(spdlog::level::info);
// buffer size for waveform:
// (44100 / fps * 10), make 10 bigger for slower scrolling
//
// Double both buffer sizes if stereo
// FFT generator
FFT_Visualizer fft = FFT_Visualizer(
width, height, MIN_HZ, MAX_HZ
);
// Audio buffer
Buffer buf = Buffer(
"/tmp/mpd.fifo",
44100 / 2, // Keep 500ms of data in buffer
fft.compute_buffer_output_size()
);
// HID interface wrapper
Ergodox Dox = Ergodox::init(
HID_VENDOR_ID,
HID_PRODUCT_ID,
HID_USAGE,
HID_USAGE_PAGE
);
// Write buffer
uint8_t hid_buf[Dox.packet_size];
uint8_t last_layer_layout;
Dox.connect_loop();
// Frame rate limiter
std::chrono::time_point<
std::chrono::steady_clock,
std::chrono::nanoseconds
> t = std::chrono::steady_clock::now();
std::chrono::time_point<
std::chrono::steady_clock,
std::chrono::nanoseconds
> last_fifo_sync = std::chrono::steady_clock::now();
struct mpd_connection *conn = mpd_connection_new(NULL, 0, 0);
while (1) {
memset(hid_buf, 0, sizeof(uint8_t) * Dox.packet_size);
if (Dox.is_connected()) {
if (std::chrono::steady_clock::now() > t + std::chrono::milliseconds(30)) {
if (Dox.get_animation_mode() == 0x02) {
// Animation data type
hid_buf[1] = CMD_ANIM_DATA_fft;
if (std::chrono::steady_clock::now() > last_fifo_sync + std::chrono::seconds(10)) {
mpd_run_disable_output(conn, 1);
mpd_run_enable_output(conn, 1);
last_fifo_sync = std::chrono::steady_clock::now();
spdlog::info("Synchronized fifo");
}
buf.update();
fft.update(buf);
for (size_t i = 0; i < 10; i++) {
// Get height from fft, apply bottom_skip
ssize_t h = fft.get_output()[i] - BOTTOM_SKIP;
// Enforce max and min
// max implicitly enforces top_skip
h = h>KB_RESOLUTION ? KB_RESOLUTION : h;
h = h<0 ? 0 : h;
hid_buf[i + 1] = h;
}
Dox.write(CMD_ANIM_DATA, hid_buf, Dox.packet_size);
}
t = std::chrono::steady_clock::now();
}
// Dox.write might detect that we've been disconnected,
// and Dox.read will fail if we are.
// This check prevents it from doing that, and instead jumps to reconnect.
if (!Dox.is_connected()) { continue; }
// Read a packet if there is a packet to read
if (Dox.read()) {
uint8_t cmd = Dox.read_buf[0];
switch(cmd) {
case CMD_SPELLCHECK_WORD:
char word_chars[Dox.read_buf[1] + 1];
if (Dox.get_layer_layout() == LAYOUT_EN) {
for (int i=0; i < Dox.read_buf[1]; i++) {
// A in ascii:
// a in ascii: 0x61
// KC_A: 0x04
word_chars[i] = Dox.read_buf[i + 2] + 0x5D;
}
} else if (Dox.get_layer_layout() == LAYOUT_RU) {
for (int i=0; i < Dox.read_buf[1]; i++) {
word_chars[i] = ru_kc_to_char(Dox.read_buf[i + 2]);
}
}
word_chars[Dox.read_buf[1]] = 0x00; // Terminate with null char
std::string word = std::string(word_chars);
int dp = hun->spell(word);
if (!dp) {
hid_buf[0] = 0x01;
Dox.write(CMD_SPELLCHECK_WORD, hid_buf, Dox.packet_size);
spdlog::info("Got typo: \"{0:s}\" not in dict", word);
}
break;
}
}
if (
(last_layer_layout != Dox.get_layer_layout()) &&
(Dox.get_layer_layout() != LAYOUT_NULL)
) {
last_layer_layout = Dox.get_layer_layout();
switch(last_layer_layout) {
case (LAYOUT_EN):
std::system("awesome-client \"modules.ibus.set('en')\"");
break;
case (LAYOUT_RU):
std::system("awesome-client \"modules.ibus.set('ru')\"");
break;
}
}
} else {
Dox.connect_loop();
}
// Sleep for a bit so we don't consume
// 100% of a cpu.
std::this_thread::sleep_for(
std::chrono::milliseconds(LOOP_SLEEP_MS)
);
}
mpd_connection_free(conn);
return 0;
}