summaryrefslogtreecommitdiff
path: root/hid/arduino
diff options
context:
space:
mode:
authorMaxim Devaev <[email protected]>2023-07-31 02:17:23 +0300
committerMaxim Devaev <[email protected]>2023-07-31 02:17:23 +0300
commit1a8f98a64f9480c1062225e0fc994ceac4ba346d (patch)
tree934ac95c6c0774d6ac512860905d4d2454b0b2aa /hid/arduino
parentcf44668af998b114fbddc8fa41b47193b606c064 (diff)
moved arduino hid to hid/arduino
Diffstat (limited to 'hid/arduino')
-rw-r--r--hid/arduino/.gitignore6
-rw-r--r--hid/arduino/Makefile51
-rw-r--r--hid/arduino/avrdude-rpi.conf7
-rw-r--r--hid/arduino/avrdude.py54
-rw-r--r--hid/arduino/lib/.gitignore0
-rw-r--r--hid/arduino/lib/drivers-avr/eeprom.h40
-rw-r--r--hid/arduino/lib/drivers-avr/factory.cpp94
-rw-r--r--hid/arduino/lib/drivers-avr/ps2/hid.h95
-rw-r--r--hid/arduino/lib/drivers-avr/ps2/keymap.h152
-rw-r--r--hid/arduino/lib/drivers-avr/ps2/keymap.h.mako44
-rw-r--r--hid/arduino/lib/drivers-avr/spi.cpp83
-rw-r--r--hid/arduino/lib/drivers-avr/spi.h40
-rw-r--r--hid/arduino/lib/drivers-avr/usb/hid.h230
-rw-r--r--hid/arduino/lib/drivers-stm32/README.md22
-rw-r--r--hid/arduino/lib/drivers-stm32/backup-register.h53
-rw-r--r--hid/arduino/lib/drivers-stm32/bluepill_sch.pngbin0 -> 179860 bytes
-rw-r--r--hid/arduino/lib/drivers-stm32/board-stm32.h103
-rw-r--r--hid/arduino/lib/drivers-stm32/factory.cpp94
-rw-r--r--hid/arduino/lib/drivers-stm32/usb/hid-wrapper-stm32.h72
-rw-r--r--hid/arduino/lib/drivers-stm32/usb/keyboard-stm32.h92
-rw-r--r--hid/arduino/lib/drivers-stm32/usb/mouse-absolute-stm32.h86
-rw-r--r--hid/arduino/lib/drivers-stm32/usb/mouse-relative-stm32.h86
-rw-r--r--hid/arduino/lib/drivers/aum.h48
-rw-r--r--hid/arduino/lib/drivers/board.h41
-rw-r--r--hid/arduino/lib/drivers/connection.h54
-rw-r--r--hid/arduino/lib/drivers/driver.h49
-rw-r--r--hid/arduino/lib/drivers/factory.h39
-rw-r--r--hid/arduino/lib/drivers/keyboard.h68
-rw-r--r--hid/arduino/lib/drivers/mouse.h51
-rw-r--r--hid/arduino/lib/drivers/serial.h68
-rw-r--r--hid/arduino/lib/drivers/storage.h35
-rw-r--r--hid/arduino/lib/drivers/tools.cpp32
-rw-r--r--hid/arduino/lib/drivers/tools.h28
-rw-r--r--hid/arduino/lib/drivers/usb-keymap.h141
-rw-r--r--hid/arduino/lib/drivers/usb-keymap.h.mako37
-rw-r--r--hid/arduino/patch.py49
-rw-r--r--hid/arduino/patches/arduino-get-plugged-endpoint.patch11
-rw-r--r--hid/arduino/patches/arduino-main-no-usb.patch24
-rw-r--r--hid/arduino/patches/arduino-optional-cdc.patch141
-rw-r--r--hid/arduino/patches/hid-no-singletones.patch66
-rw-r--r--hid/arduino/patches/hid-shut-up.patch16
-rw-r--r--hid/arduino/patches/hid-win98.patch82
-rw-r--r--hid/arduino/patches/platformio-stm32f1-no-serial-usb.patch11
-rw-r--r--hid/arduino/platformio-avr.ini119
-rw-r--r--hid/arduino/platformio-stm32.ini52
-rw-r--r--hid/arduino/src/main.cpp253
-rw-r--r--hid/arduino/src/outputs.h122
-rw-r--r--hid/arduino/src/proto.h142
48 files changed, 3283 insertions, 0 deletions
diff --git a/hid/arduino/.gitignore b/hid/arduino/.gitignore
new file mode 100644
index 00000000..05b843bc
--- /dev/null
+++ b/hid/arduino/.gitignore
@@ -0,0 +1,6 @@
+/.platformio/
+/.pio/
+/.current
+/.vscode/
+/.config
+/platformio.ini
diff --git a/hid/arduino/Makefile b/hid/arduino/Makefile
new file mode 100644
index 00000000..652ddd6e
--- /dev/null
+++ b/hid/arduino/Makefile
@@ -0,0 +1,51 @@
+serial:
+ make _build E=serial C=avr
+spi:
+ make _build E=spi C=avr
+aum:
+ make _build E=aum C=avr
+stm32:
+ platformio run --environment patch --project-conf platformio-stm32.ini
+ make _build E=serial C=stm32
+_build:
+ rm -f .current .config
+ platformio run --environment $(E) --project-conf platformio-$(C).ini
+ echo -n $(E) > .current
+ echo -n $(C) > .config
+
+# Added to easy test all builds
+_build_all: aum spi serial stm32
+ rm -f .current .config
+
+install: upload
+upload:
+ $(eval $@_CURRENT := $(shell cat .current))
+ $(eval $@_CONFIG := $(shell cat .config))
+ bash -ex -c " \
+ current=`cat .current`; \
+ if [ '$($@_CURRENT)' == 'spi' ] || [ '$($@_CURRENT)' == 'aum' ]; then \
+ gpioset 0 25=1; \
+ gpioset 0 25=0; \
+ fi \
+ "
+ platformio run --environment '$($@_CURRENT)' --project-conf 'platformio-$($@_CONFIG).ini' --target upload
+
+
+bootloader-spi: install-bootloader-spi
+install-bootloader-spi: upload-bootloader-spi
+upload-bootloader-spi:
+ platformio run --environment bootloader_spi --project-conf platformio-avr.ini --target bootloader
+
+
+update:
+ platformio platform update
+
+
+clean-all: clean
+ rm -rf .platformio
+clean:
+ rm -rf .pio .current .config platformio.ini
+
+
+help:
+ @ cat Makefile
diff --git a/hid/arduino/avrdude-rpi.conf b/hid/arduino/avrdude-rpi.conf
new file mode 100644
index 00000000..8a6f5460
--- /dev/null
+++ b/hid/arduino/avrdude-rpi.conf
@@ -0,0 +1,7 @@
+programmer
+ id = "rpi";
+ desc = "RPi SPI programmer";
+ type = "linuxspi";
+ reset = 25;
+ baudrate = 400000;
+;
diff --git a/hid/arduino/avrdude.py b/hid/arduino/avrdude.py
new file mode 100644
index 00000000..3ae227f9
--- /dev/null
+++ b/hid/arduino/avrdude.py
@@ -0,0 +1,54 @@
+# https://docs.platformio.org/en/latest/projectconf/advanced_scripting.html
+
+
+from os import rename
+from os import symlink
+from os.path import exists
+from os.path import join
+
+import platform
+
+Import("env")
+
+
+# =====
+def _get_tool_path() -> str:
+ path = env.PioPlatform().get_package_dir("tool-avrdude")
+ assert exists(path)
+ return path
+
+
+def _fix_ld_arm() -> None:
+ tool_path = _get_tool_path()
+ flag_path = join(tool_path, ".fix-ld-arm.done")
+
+ if not exists(flag_path):
+ def patch(*_, **__) -> None:
+ symlink("/usr/lib/libtinfo.so.6", join(tool_path, "libtinfo.so.5"))
+ open(flag_path, "w").close()
+
+ env.Execute(patch)
+
+
+def _replace_to_system(new_path: str) -> None:
+ tool_path = _get_tool_path()
+ flag_path = join(tool_path, ".replace-to-system.done")
+
+ if not exists(flag_path):
+ def patch(*_, **__) -> None:
+ old_path = join(tool_path, "avrdude")
+ bak_path = join(tool_path, "_avrdude_bak")
+ rename(old_path, bak_path)
+ symlink(new_path, old_path)
+ open(flag_path, "w").close()
+
+ env.Execute(patch)
+
+
+# =====
+if "arm" in platform.machine():
+ _fix_ld_arm()
+
+_path = "/usr/bin/avrdude"
+if exists(_path):
+ _replace_to_system(_path)
diff --git a/hid/arduino/lib/.gitignore b/hid/arduino/lib/.gitignore
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/hid/arduino/lib/.gitignore
diff --git a/hid/arduino/lib/drivers-avr/eeprom.h b/hid/arduino/lib/drivers-avr/eeprom.h
new file mode 100644
index 00000000..bac642a7
--- /dev/null
+++ b/hid/arduino/lib/drivers-avr/eeprom.h
@@ -0,0 +1,40 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#include <avr/eeprom.h>
+
+#include "storage.h"
+
+
+namespace DRIVERS {
+ struct Eeprom : public Storage {
+ using Storage::Storage;
+
+ void readBlock(void *dest, const void *src, size_t size) override {
+ eeprom_read_block(dest, src, size);
+ }
+
+ void updateBlock(const void *src, void *dest, size_t size) override {
+ eeprom_update_block(src, dest, size);
+ }
+ };
+}
diff --git a/hid/arduino/lib/drivers-avr/factory.cpp b/hid/arduino/lib/drivers-avr/factory.cpp
new file mode 100644
index 00000000..801e7a2f
--- /dev/null
+++ b/hid/arduino/lib/drivers-avr/factory.cpp
@@ -0,0 +1,94 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#include "usb/hid.h"
+#include "ps2/hid.h"
+#include "factory.h"
+#include "eeprom.h"
+#include "serial.h"
+#include "spi.h"
+
+#ifndef ARDUINO_ARCH_AVR
+# error "Only AVR is supported"
+#endif
+
+
+namespace DRIVERS {
+ Keyboard *Factory::makeKeyboard(type _type) {
+ switch (_type) {
+# ifdef HID_WITH_USB
+ case USB_KEYBOARD:
+ return new UsbKeyboard();
+# endif
+
+# ifdef HID_WITH_PS2
+ case PS2_KEYBOARD:
+ return new Ps2Keyboard();
+# endif
+
+ default:
+ return new Keyboard(DUMMY);
+ }
+ }
+
+ Mouse *Factory::makeMouse(type _type) {
+ switch (_type) {
+# ifdef HID_WITH_USB
+ case USB_MOUSE_ABSOLUTE:
+ case USB_MOUSE_ABSOLUTE_WIN98:
+ return new UsbMouseAbsolute(_type);
+ case USB_MOUSE_RELATIVE:
+ return new UsbMouseRelative();
+# endif
+ default:
+ return new Mouse(DRIVERS::DUMMY);
+ }
+ }
+
+ Storage *Factory::makeStorage(type _type) {
+ switch (_type) {
+# ifdef HID_DYNAMIC
+ case NON_VOLATILE_STORAGE:
+ return new Eeprom(DRIVERS::NON_VOLATILE_STORAGE);
+# endif
+ default:
+ return new Storage(DRIVERS::DUMMY);
+ }
+ }
+
+ Board *Factory::makeBoard(type _type) {
+ switch (_type) {
+ default:
+ return new Board(DRIVERS::DUMMY);
+ }
+ }
+
+ Connection *Factory::makeConnection(type _type) {
+# ifdef CMD_SERIAL
+ return new Serial();
+# elif defined(CMD_SPI)
+ return new Spi();
+# else
+# error CMD phy is not defined
+# endif
+ }
+}
diff --git a/hid/arduino/lib/drivers-avr/ps2/hid.h b/hid/arduino/lib/drivers-avr/ps2/hid.h
new file mode 100644
index 00000000..ecd64d16
--- /dev/null
+++ b/hid/arduino/lib/drivers-avr/ps2/hid.h
@@ -0,0 +1,95 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#include <Arduino.h>
+#include <ps2dev.h>
+
+#include "keyboard.h"
+#include "keymap.h"
+
+// #define HID_PS2_KBD_CLOCK_PIN 7
+// #define HID_PS2_KBD_DATA_PIN 5
+
+
+class Ps2Keyboard : public DRIVERS::Keyboard {
+ // https://wiki.osdev.org/PS/2_Keyboard
+
+ public:
+ Ps2Keyboard() : DRIVERS::Keyboard(DRIVERS::PS2_KEYBOARD), _dev(HID_PS2_KBD_CLOCK_PIN, HID_PS2_KBD_DATA_PIN) {}
+
+ void begin() override {
+ _dev.keyboard_init();
+ }
+
+ void periodic() override {
+ _dev.keyboard_handle(&_leds);
+ }
+
+ void sendKey(uint8_t code, bool state) override {
+ Ps2KeyType ps2_type;
+ uint8_t ps2_code;
+
+ keymapPs2(code, &ps2_type, &ps2_code);
+ if (ps2_type != PS2_KEY_TYPE_UNKNOWN) {
+ // Не отправлялась часть нажатий. Когда clock на нуле, комп не принимает ничего от клавы.
+ // Этот костыль понижает процент пропущенных нажатий.
+ while (digitalRead(HID_PS2_KBD_CLOCK_PIN) == 0) {};
+ if (state) {
+ switch (ps2_type) {
+ case PS2_KEY_TYPE_REG: _dev.keyboard_press(ps2_code); break;
+ case PS2_KEY_TYPE_SPEC: _dev.keyboard_press_special(ps2_code); break;
+ case PS2_KEY_TYPE_PRINT: _dev.keyboard_press_printscreen(); break;
+ case PS2_KEY_TYPE_PAUSE: _dev.keyboard_pausebreak(); break;
+ case PS2_KEY_TYPE_UNKNOWN: break;
+ }
+ } else {
+ switch (ps2_type) {
+ case PS2_KEY_TYPE_REG: _dev.keyboard_release(ps2_code); break;
+ case PS2_KEY_TYPE_SPEC: _dev.keyboard_release_special(ps2_code); break;
+ case PS2_KEY_TYPE_PRINT: _dev.keyboard_release_printscreen(); break;
+ case PS2_KEY_TYPE_PAUSE: break;
+ case PS2_KEY_TYPE_UNKNOWN: break;
+ }
+ }
+ }
+ }
+
+ bool isOffline() override {
+ return false;
+ }
+
+ KeyboardLedsState getLeds() override {
+ periodic();
+ KeyboardLedsState result = {
+ .caps = _leds & 0b00000100,
+ .scroll = _leds & 0b00000001,
+ .num = _leds & 0b00000010,
+ };
+ return result;
+ }
+
+ private:
+ PS2dev _dev;
+ uint8_t _leds = 0;
+};
diff --git a/hid/arduino/lib/drivers-avr/ps2/keymap.h b/hid/arduino/lib/drivers-avr/ps2/keymap.h
new file mode 100644
index 00000000..65a5d7a1
--- /dev/null
+++ b/hid/arduino/lib/drivers-avr/ps2/keymap.h
@@ -0,0 +1,152 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+
+enum Ps2KeyType : uint8_t {
+ PS2_KEY_TYPE_UNKNOWN = 0,
+ PS2_KEY_TYPE_REG = 1,
+ PS2_KEY_TYPE_SPEC = 2,
+ PS2_KEY_TYPE_PRINT = 3,
+ PS2_KEY_TYPE_PAUSE = 4,
+};
+
+
+void keymapPs2(uint8_t code, Ps2KeyType *ps2_type, uint8_t *ps2_code) {
+ *ps2_type = PS2_KEY_TYPE_UNKNOWN;
+ *ps2_code = 0;
+
+ switch (code) {
+ case 1: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 28; return; // KeyA
+ case 2: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 50; return; // KeyB
+ case 3: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 33; return; // KeyC
+ case 4: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 35; return; // KeyD
+ case 5: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 36; return; // KeyE
+ case 6: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 43; return; // KeyF
+ case 7: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 52; return; // KeyG
+ case 8: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 51; return; // KeyH
+ case 9: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 67; return; // KeyI
+ case 10: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 59; return; // KeyJ
+ case 11: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 66; return; // KeyK
+ case 12: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 75; return; // KeyL
+ case 13: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 58; return; // KeyM
+ case 14: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 49; return; // KeyN
+ case 15: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 68; return; // KeyO
+ case 16: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 77; return; // KeyP
+ case 17: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 21; return; // KeyQ
+ case 18: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 45; return; // KeyR
+ case 19: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 27; return; // KeyS
+ case 20: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 44; return; // KeyT
+ case 21: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 60; return; // KeyU
+ case 22: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 42; return; // KeyV
+ case 23: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 29; return; // KeyW
+ case 24: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 34; return; // KeyX
+ case 25: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 53; return; // KeyY
+ case 26: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 26; return; // KeyZ
+ case 27: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 22; return; // Digit1
+ case 28: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 30; return; // Digit2
+ case 29: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 38; return; // Digit3
+ case 30: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 37; return; // Digit4
+ case 31: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 46; return; // Digit5
+ case 32: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 54; return; // Digit6
+ case 33: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 61; return; // Digit7
+ case 34: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 62; return; // Digit8
+ case 35: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 70; return; // Digit9
+ case 36: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 69; return; // Digit0
+ case 37: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 90; return; // Enter
+ case 38: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 118; return; // Escape
+ case 39: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 102; return; // Backspace
+ case 40: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 13; return; // Tab
+ case 41: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 41; return; // Space
+ case 42: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 78; return; // Minus
+ case 43: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 85; return; // Equal
+ case 44: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 84; return; // BracketLeft
+ case 45: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 91; return; // BracketRight
+ case 46: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 93; return; // Backslash
+ case 47: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 76; return; // Semicolon
+ case 48: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 82; return; // Quote
+ case 49: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 14; return; // Backquote
+ case 50: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 65; return; // Comma
+ case 51: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 73; return; // Period
+ case 52: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 74; return; // Slash
+ case 53: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 88; return; // CapsLock
+ case 54: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 5; return; // F1
+ case 55: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 6; return; // F2
+ case 56: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 4; return; // F3
+ case 57: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 12; return; // F4
+ case 58: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 3; return; // F5
+ case 59: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 11; return; // F6
+ case 60: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 131; return; // F7
+ case 61: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 10; return; // F8
+ case 62: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 1; return; // F9
+ case 63: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 9; return; // F10
+ case 64: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 120; return; // F11
+ case 65: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 7; return; // F12
+ case 66: *ps2_type = PS2_KEY_TYPE_PRINT; *ps2_code = 255; return; // PrintScreen
+ case 67: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 112; return; // Insert
+ case 68: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 108; return; // Home
+ case 69: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 125; return; // PageUp
+ case 70: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 113; return; // Delete
+ case 71: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 105; return; // End
+ case 72: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 122; return; // PageDown
+ case 73: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 116; return; // ArrowRight
+ case 74: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 107; return; // ArrowLeft
+ case 75: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 114; return; // ArrowDown
+ case 76: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 117; return; // ArrowUp
+ case 77: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 20; return; // ControlLeft
+ case 78: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 18; return; // ShiftLeft
+ case 79: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 17; return; // AltLeft
+ case 80: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 31; return; // MetaLeft
+ case 81: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 20; return; // ControlRight
+ case 82: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 89; return; // ShiftRight
+ case 83: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 17; return; // AltRight
+ case 84: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 39; return; // MetaRight
+ case 85: *ps2_type = PS2_KEY_TYPE_PAUSE; *ps2_code = 255; return; // Pause
+ case 86: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 126; return; // ScrollLock
+ case 87: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 119; return; // NumLock
+ case 88: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 47; return; // ContextMenu
+ case 89: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 74; return; // NumpadDivide
+ case 90: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 124; return; // NumpadMultiply
+ case 91: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 123; return; // NumpadSubtract
+ case 92: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 121; return; // NumpadAdd
+ case 93: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 90; return; // NumpadEnter
+ case 94: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 105; return; // Numpad1
+ case 95: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 114; return; // Numpad2
+ case 96: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 122; return; // Numpad3
+ case 97: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 107; return; // Numpad4
+ case 98: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 115; return; // Numpad5
+ case 99: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 116; return; // Numpad6
+ case 100: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 108; return; // Numpad7
+ case 101: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 117; return; // Numpad8
+ case 102: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 125; return; // Numpad9
+ case 103: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 112; return; // Numpad0
+ case 104: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 113; return; // NumpadDecimal
+ case 105: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 94; return; // Power
+ case 106: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 97; return; // IntlBackslash
+ case 107: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 106; return; // IntlYen
+ case 108: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 81; return; // IntlRo
+ case 109: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 19; return; // KanaMode
+ case 110: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 100; return; // Convert
+ case 111: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 103; return; // NonConvert
+ }
+}
diff --git a/hid/arduino/lib/drivers-avr/ps2/keymap.h.mako b/hid/arduino/lib/drivers-avr/ps2/keymap.h.mako
new file mode 100644
index 00000000..75dfd8c2
--- /dev/null
+++ b/hid/arduino/lib/drivers-avr/ps2/keymap.h.mako
@@ -0,0 +1,44 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+
+enum Ps2KeyType : uint8_t {
+ PS2_KEY_TYPE_UNKNOWN = 0,
+ PS2_KEY_TYPE_REG = 1,
+ PS2_KEY_TYPE_SPEC = 2,
+ PS2_KEY_TYPE_PRINT = 3,
+ PS2_KEY_TYPE_PAUSE = 4,
+};
+
+<%! import operator %>
+void keymapPs2(uint8_t code, Ps2KeyType *ps2_type, uint8_t *ps2_code) {
+ *ps2_type = PS2_KEY_TYPE_UNKNOWN;
+ *ps2_code = 0;
+
+ switch (code) {
+% for km in sorted(keymap, key=operator.attrgetter("mcu_code")):
+ case ${km.mcu_code}: *ps2_type = PS2_KEY_TYPE_${km.ps2_key.type.upper()}; *ps2_code = ${km.ps2_key.code}; return; // ${km.web_name}
+% endfor
+ }
+}
diff --git a/hid/arduino/lib/drivers-avr/spi.cpp b/hid/arduino/lib/drivers-avr/spi.cpp
new file mode 100644
index 00000000..f9cab4ac
--- /dev/null
+++ b/hid/arduino/lib/drivers-avr/spi.cpp
@@ -0,0 +1,83 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#include "spi.h"
+
+#ifdef CMD_SPI
+
+
+static volatile uint8_t _spi_in[8] = {0};
+static volatile uint8_t _spi_in_index = 0;
+
+static volatile uint8_t _spi_out[8] = {0};
+static volatile uint8_t _spi_out_index = 0;
+
+
+namespace DRIVERS {
+ void Spi::begin() {
+ pinMode(MISO, OUTPUT);
+ SPCR = (1 << SPE) | (1 << SPIE); // Slave, SPI En, IRQ En
+ }
+
+ void Spi::periodic() {
+ if (!_spi_out[0] && _spi_in_index == 8) {
+ _data_cb((const uint8_t *)_spi_in, 8);
+ }
+ }
+
+ void Spi::write(const uint8_t *data, size_t size) {
+ // Меджик в нулевом байте разрешает начать ответ
+ for (int index = 7; index >= 0; --index) {
+ _spi_out[index] = data[index];
+ }
+ }
+}
+
+ISR(SPI_STC_vect) {
+ uint8_t in = SPDR;
+ if (_spi_out[0] && _spi_out_index < 8) {
+ SPDR = _spi_out[_spi_out_index];
+ if (!(SPSR & (1 << WCOL))) {
+ ++_spi_out_index;
+ if (_spi_out_index == 8) {
+ _spi_out_index = 0;
+ _spi_in_index = 0;
+ _spi_out[0] = 0;
+ }
+ }
+ } else {
+ static bool receiving = false;
+ if (!receiving && in != 0) {
+ receiving = true;
+ }
+ if (receiving && _spi_in_index < 8) {
+ _spi_in[_spi_in_index] = in;
+ ++_spi_in_index;
+ }
+ if (_spi_in_index == 8) {
+ receiving = false;
+ }
+ SPDR = 0;
+ }
+}
+
+#endif
diff --git a/hid/arduino/lib/drivers-avr/spi.h b/hid/arduino/lib/drivers-avr/spi.h
new file mode 100644
index 00000000..368895c2
--- /dev/null
+++ b/hid/arduino/lib/drivers-avr/spi.h
@@ -0,0 +1,40 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#include <Arduino.h>
+
+#include "connection.h"
+
+
+namespace DRIVERS {
+ struct Spi : public Connection {
+ Spi() : Connection(CONNECTION) {}
+
+ void begin() override;
+
+ void periodic() override;
+
+ void write(const uint8_t *data, size_t size) override;
+ };
+}
diff --git a/hid/arduino/lib/drivers-avr/usb/hid.h b/hid/arduino/lib/drivers-avr/usb/hid.h
new file mode 100644
index 00000000..aa7b7ba0
--- /dev/null
+++ b/hid/arduino/lib/drivers-avr/usb/hid.h
@@ -0,0 +1,230 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#include <Arduino.h>
+#include <HID-Project.h>
+
+#include "keyboard.h"
+#include "mouse.h"
+#include "tools.h"
+#include "usb-keymap.h"
+#ifdef AUM
+# include "aum.h"
+#endif
+
+using namespace DRIVERS;
+
+// -----------------------------------------------------------------------------
+#ifdef HID_USB_CHECK_ENDPOINT
+// https://github.com/arduino/ArduinoCore-avr/blob/2f67c916f6ab6193c404eebe22efe901e0f9542d/cores/arduino/USBCore.cpp#L249
+// https://sourceforge.net/p/arduinomidilib/svn/41/tree/branch/3.1/Teensy/teensy_core/usb_midi/usb_api.cpp#l103
+# ifdef AUM
+# define CHECK_AUM_USB { if (!aumIsUsbConnected()) { return true; } }
+# else
+# define CHECK_AUM_USB
+# endif
+# define CLS_IS_OFFLINE(_hid) \
+ bool isOffline() override { \
+ CHECK_AUM_USB; \
+ uint8_t ep = _hid.getPluggedEndpoint(); \
+ uint8_t intr_state = SREG; \
+ cli(); \
+ UENUM = ep & 7; \
+ bool rw_allowed = UEINTX & (1 << RWAL); \
+ SREG = intr_state; \
+ if (rw_allowed) { \
+ return false; \
+ } \
+ return true; \
+ }
+# define CHECK_HID_EP { if (isOffline()) return; }
+
+#else
+# define CLS_IS_OFFLINE(_hid) \
+ bool isOffline() override { \
+ return false; \
+ }
+# define CHECK_HID_EP
+
+#endif
+
+
+class UsbKeyboard : public DRIVERS::Keyboard {
+ public:
+ UsbKeyboard() : DRIVERS::Keyboard(DRIVERS::USB_KEYBOARD) {}
+
+ void begin() override {
+ _kbd.begin();
+ }
+
+ void periodic() override {
+# ifdef HID_USB_CHECK_ENDPOINT
+ static unsigned long prev_ts = 0;
+ if (is_micros_timed_out(prev_ts, 50000)) {
+ static bool prev_online = true;
+ bool online = !isOffline();
+ if (!_sent || (online && !prev_online)) {
+ _sendCurrent();
+ }
+ prev_online = online;
+ prev_ts = micros();
+ }
+# endif
+ }
+
+ void clear() override {
+ _kbd.releaseAll();
+ }
+
+ void sendKey(uint8_t code, bool state) override {
+ enum KeyboardKeycode usb_code = keymapUsb(code);
+ if (usb_code > 0) {
+ if (state ? _kbd.add(usb_code) : _kbd.remove(usb_code)) {
+ _sendCurrent();
+ }
+ }
+ }
+
+ CLS_IS_OFFLINE(_kbd)
+
+ KeyboardLedsState getLeds() override {
+ uint8_t leds = _kbd.getLeds();
+ KeyboardLedsState result = {
+ .caps = leds & LED_CAPS_LOCK,
+ .scroll = leds & LED_SCROLL_LOCK,
+ .num = leds & LED_NUM_LOCK,
+ };
+ return result;
+ }
+
+ private:
+ BootKeyboard_ _kbd;
+ bool _sent = true;
+
+ void _sendCurrent() {
+# ifdef HID_USB_CHECK_ENDPOINT
+ if (isOffline()) {
+ _sent = false;
+ } else {
+# endif
+ _sent = (_kbd.send() >= 0);
+# ifdef HID_USB_CHECK_ENDPOINT
+ }
+# endif
+ }
+};
+
+#define CLS_SEND_BUTTONS \
+ void sendButtons( \
+ bool left_select, bool left_state, \
+ bool right_select, bool right_state, \
+ bool middle_select, bool middle_state, \
+ bool up_select, bool up_state, \
+ bool down_select, bool down_state \
+ ) override { \
+ if (left_select) _sendButton(MOUSE_LEFT, left_state); \
+ if (right_select) _sendButton(MOUSE_RIGHT, right_state); \
+ if (middle_select) _sendButton(MOUSE_MIDDLE, middle_state); \
+ if (up_select) _sendButton(MOUSE_PREV, up_state); \
+ if (down_select) _sendButton(MOUSE_NEXT, down_state); \
+ }
+
+class UsbMouseAbsolute : public DRIVERS::Mouse {
+ public:
+ UsbMouseAbsolute(DRIVERS::type _type) : Mouse(_type) {}
+
+ void begin() override {
+ _mouse.begin();
+ _mouse.setWin98FixEnabled(getType() == DRIVERS::USB_MOUSE_ABSOLUTE_WIN98);
+ }
+
+ void clear() override {
+ _mouse.releaseAll();
+ }
+
+ CLS_SEND_BUTTONS
+
+ void sendMove(int x, int y) override {
+ CHECK_HID_EP;
+ _mouse.moveTo(x, y);
+ }
+
+ void sendWheel(int delta_y) override {
+ // delta_x is not supported by hid-project now
+ CHECK_HID_EP;
+ _mouse.move(0, 0, delta_y);
+ }
+
+ CLS_IS_OFFLINE(_mouse)
+
+ private:
+ SingleAbsoluteMouse_ _mouse;
+
+ void _sendButton(uint8_t button, bool state) {
+ CHECK_HID_EP;
+ if (state) _mouse.press(button);
+ else _mouse.release(button);
+ }
+};
+
+class UsbMouseRelative : public DRIVERS::Mouse {
+ public:
+ UsbMouseRelative() : DRIVERS::Mouse(DRIVERS::USB_MOUSE_RELATIVE) {}
+
+ void begin() override {
+ _mouse.begin();
+ }
+
+ void clear() override {
+ _mouse.releaseAll();
+ }
+
+ CLS_SEND_BUTTONS
+
+ void sendRelative(int x, int y) override {
+ CHECK_HID_EP;
+ _mouse.move(x, y, 0);
+ }
+
+ void sendWheel(int delta_y) override {
+ // delta_x is not supported by hid-project now
+ CHECK_HID_EP;
+ _mouse.move(0, 0, delta_y);
+ }
+
+ CLS_IS_OFFLINE(_mouse)
+
+ private:
+ BootMouse_ _mouse;
+
+ void _sendButton(uint8_t button, bool state) {
+ CHECK_HID_EP;
+ if (state) _mouse.press(button);
+ else _mouse.release(button);
+ }
+};
+
+#undef CLS_SEND_BUTTONS
+#undef CLS_IS_OFFLINE
+#undef CHECK_HID_EP
diff --git a/hid/arduino/lib/drivers-stm32/README.md b/hid/arduino/lib/drivers-stm32/README.md
new file mode 100644
index 00000000..93de3312
--- /dev/null
+++ b/hid/arduino/lib/drivers-stm32/README.md
@@ -0,0 +1,22 @@
+This is WIP. Use AVR version as reference.
+
+It was tested with bluepill. Most boards are clones.
+If you have problem with USB please check https://stm32duinoforum.com/forum/wiki_subdomain/index_title_Blue_Pill.html for pull up resistor. If it still does not work check another board or cable.
+
+TODO:
+- [x] Serial communication
+- [x] USB keyboard
+- [x] USB keyboard - add scroll status
+- [x] USB keyboard - key sending
+- [x] USB keyboard - test key mapping
+- [x] Persistent storage
+- [ ] SPI communication
+- [ ] PS2 keyboard
+- [x] USB absolute mouse
+- [x] USB absolute mouse - add whele
+- [x] USB relative mouse
+- [x] USB relative mouse - add whele
+- [ ] USB mouses - up down button
+- [ ] WIN98 USB mouse
+- [x] undefine SERIAL_USB
+- [ ] boot keyboard
diff --git a/hid/arduino/lib/drivers-stm32/backup-register.h b/hid/arduino/lib/drivers-stm32/backup-register.h
new file mode 100644
index 00000000..196e4e1c
--- /dev/null
+++ b/hid/arduino/lib/drivers-stm32/backup-register.h
@@ -0,0 +1,53 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+# pragma once
+
+
+#include <stm32f1_rtc.h>
+
+#include "storage.h"
+
+
+namespace DRIVERS {
+ struct BackupRegister : public Storage {
+ BackupRegister() : Storage(NON_VOLATILE_STORAGE) {
+ _rtc.enableClockInterface();
+ }
+
+ void readBlock(void *dest, const void *src, size_t size) override {
+ uint8_t *dest_ = reinterpret_cast<uint8_t*>(dest);
+ for(size_t index = 0; index < size; ++index) {
+ dest_[index] = _rtc.getBackupRegister(reinterpret_cast<uintptr_t>(src) + index + 1);
+ }
+ }
+
+ void updateBlock(const void *src, void *dest, size_t size) override {
+ const uint8_t *src_ = reinterpret_cast<const uint8_t*>(src);
+ for(size_t index = 0; index < size; ++index) {
+ _rtc.setBackupRegister(reinterpret_cast<uintptr_t>(dest) + index + 1, src_[index]);
+ }
+ }
+
+ private:
+ STM32F1_RTC _rtc;
+ };
+}
diff --git a/hid/arduino/lib/drivers-stm32/bluepill_sch.png b/hid/arduino/lib/drivers-stm32/bluepill_sch.png
new file mode 100644
index 00000000..956448dd
--- /dev/null
+++ b/hid/arduino/lib/drivers-stm32/bluepill_sch.png
Binary files differ
diff --git a/hid/arduino/lib/drivers-stm32/board-stm32.h b/hid/arduino/lib/drivers-stm32/board-stm32.h
new file mode 100644
index 00000000..1602edaa
--- /dev/null
+++ b/hid/arduino/lib/drivers-stm32/board-stm32.h
@@ -0,0 +1,103 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#include "board.h"
+#include <libmaple/iwdg.h>
+
+
+namespace DRIVERS {
+ class BoardStm32 : public Board {
+ public:
+ BoardStm32() : Board(BOARD){
+ //2 sec timeout
+ iwdg_init(IWDG_PRE_16, 0xFFF);
+ pinMode(LED_BUILTIN, OUTPUT);
+ }
+
+ void reset() override {
+ nvic_sys_reset();
+ }
+
+ void periodic() override {
+ iwdg_feed();
+ if (is_micros_timed_out(_prev_ts, 100000)) {
+ switch(_state) {
+ case 0:
+ digitalWrite(LED_BUILTIN, LOW);
+ break;
+ case 2:
+ if(_rx_data) {
+ _rx_data = false;
+ digitalWrite(LED_BUILTIN, LOW);
+ }
+ break;
+ case 4:
+ if(_keyboard_online) {
+ _keyboard_online = false;
+ digitalWrite(LED_BUILTIN, LOW);
+ }
+ break;
+ case 8:
+ if(_mouse_online) {
+ _mouse_online = false;
+ digitalWrite(LED_BUILTIN, LOW);
+ }
+ break;
+ case 1: // heartbeat off
+ case 3: // _rx_data off
+ case 7: // _keyboard_online off
+ case 11: // _mouse_online off
+ digitalWrite(LED_BUILTIN, HIGH);
+ break;
+ case 19:
+ _state = -1;
+ break;
+ }
+ ++_state;
+ _prev_ts = micros();
+ }
+ }
+
+ void updateStatus(status status) override {
+ switch (status) {
+ case RX_DATA:
+ _rx_data = true;
+ break;
+ case KEYBOARD_ONLINE:
+ _keyboard_online = true;
+ break;
+ case MOUSE_ONLINE:
+ _mouse_online = true;
+ break;
+ }
+ }
+
+ private:
+ unsigned long _prev_ts = 0;
+ uint8_t _state = 0;
+ bool _rx_data = false;
+ bool _keyboard_online = false;
+ bool _mouse_online = false;
+ };
+}
diff --git a/hid/arduino/lib/drivers-stm32/factory.cpp b/hid/arduino/lib/drivers-stm32/factory.cpp
new file mode 100644
index 00000000..8f20e0ff
--- /dev/null
+++ b/hid/arduino/lib/drivers-stm32/factory.cpp
@@ -0,0 +1,94 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#include "factory.h"
+#include "usb/keyboard-stm32.h"
+#include "usb/hid-wrapper-stm32.h"
+#include "usb/mouse-absolute-stm32.h"
+#include "usb/mouse-relative-stm32.h"
+#include "backup-register.h"
+#include "board-stm32.h"
+#include "serial.h"
+
+#ifndef __STM32F1__
+# error "Only STM32F1 is supported"
+#endif
+#ifdef SERIAL_USB
+# error "Disable random USB enumeration"
+#endif
+
+
+namespace DRIVERS {
+ HidWrapper _hidWrapper;
+
+ Keyboard *Factory::makeKeyboard(type _type) {
+ switch (_type) {
+# ifdef HID_WITH_USB
+ case USB_KEYBOARD:
+ return new UsbKeyboard(_hidWrapper);
+# endif
+ default:
+ return new Keyboard(DUMMY);
+ }
+ }
+
+ Mouse *Factory::makeMouse(type _type) {
+ switch(_type) {
+# ifdef HID_WITH_USB
+ case USB_MOUSE_ABSOLUTE:
+ return new UsbMouseAbsolute(_hidWrapper);
+ case USB_MOUSE_RELATIVE:
+ return new UsbMouseRelative(_hidWrapper);
+# endif
+ default:
+ return new Mouse(DRIVERS::DUMMY);
+ }
+ }
+
+ Storage *Factory::makeStorage(type _type) {
+ switch (_type) {
+# ifdef HID_DYNAMIC
+ case NON_VOLATILE_STORAGE:
+ return new BackupRegister();
+# endif
+ default:
+ return new Storage(DRIVERS::DUMMY);
+ }
+ }
+
+ Board *Factory::makeBoard(type _type) {
+ switch (_type) {
+ case BOARD:
+ return new BoardStm32();
+ default:
+ return new Board(DRIVERS::DUMMY);
+ }
+ }
+
+ Connection *Factory::makeConnection(type _type) {
+# ifdef CMD_SERIAL
+ return new Serial();
+# else
+# error CMD phy is not defined
+# endif
+ }
+}
diff --git a/hid/arduino/lib/drivers-stm32/usb/hid-wrapper-stm32.h b/hid/arduino/lib/drivers-stm32/usb/hid-wrapper-stm32.h
new file mode 100644
index 00000000..62f58213
--- /dev/null
+++ b/hid/arduino/lib/drivers-stm32/usb/hid-wrapper-stm32.h
@@ -0,0 +1,72 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#include <USBComposite.h>
+
+
+namespace DRIVERS {
+ class HidWrapper {
+ public:
+ void begin() {
+ if (_init) {
+ return;
+ }
+ _init = true;
+
+ _report_descriptor_length = 0;
+ for (unsigned index = 0; index < _count; ++index) {
+ _report_descriptor_length += _descriptors_size[index];
+ }
+
+ _report_descriptor = new uint8[_report_descriptor_length];
+
+ size_t offset = 0;
+ for (unsigned index = 0; index < _count; ++index) {
+ memcpy(_report_descriptor + offset, _report_descriptors[index], _descriptors_size[index]);
+ offset += _descriptors_size[index];
+ }
+
+ usbHid.begin(_report_descriptor, _report_descriptor_length);
+ }
+
+ void addReportDescriptor(const uint8_t *report_descriptor, uint16_t report_descriptor_length) {
+ _report_descriptors[_count] = report_descriptor;
+ _descriptors_size[_count] = report_descriptor_length;
+ ++_count;
+ }
+
+ USBHID usbHid;
+
+ private:
+ bool _init = false;
+
+ static constexpr uint8_t MAX_USB_DESCRIPTORS = 2;
+ const uint8_t *_report_descriptors[MAX_USB_DESCRIPTORS];
+ uint8_t _descriptors_size[MAX_USB_DESCRIPTORS];
+
+ uint8_t _count = 0;
+ uint8_t *_report_descriptor;
+ uint16_t _report_descriptor_length;
+ };
+}
diff --git a/hid/arduino/lib/drivers-stm32/usb/keyboard-stm32.h b/hid/arduino/lib/drivers-stm32/usb/keyboard-stm32.h
new file mode 100644
index 00000000..22dec04d
--- /dev/null
+++ b/hid/arduino/lib/drivers-stm32/usb/keyboard-stm32.h
@@ -0,0 +1,92 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#include <USBComposite.h>
+
+#include "tools.h"
+#include "keyboard.h"
+#include "usb-keymap.h"
+#include "hid-wrapper-stm32.h"
+
+
+namespace DRIVERS {
+ const uint8_t reportDescriptionKeyboard[] = {
+ HID_KEYBOARD_REPORT_DESCRIPTOR(),
+ };
+
+ class UsbKeyboard : public Keyboard {
+ public:
+ UsbKeyboard(HidWrapper& _hidWrapper) : Keyboard(USB_KEYBOARD),
+ _hidWrapper(_hidWrapper), _keyboard(_hidWrapper.usbHid) {
+ _hidWrapper.addReportDescriptor(reportDescriptionKeyboard, sizeof(reportDescriptionKeyboard));
+ }
+
+ void begin() override {
+ _hidWrapper.begin();
+ _keyboard.begin();
+ }
+
+ void clear() override {
+ _keyboard.releaseAll();
+ }
+
+ void sendKey(uint8_t code, bool state) override {
+ uint16_t usb_code = keymapUsb(code);
+ if (usb_code == 0) {
+ return;
+ }
+
+ // 0xE0 is a prefix from HID-Project keytable
+ if (usb_code >= 0xE0 && usb_code <= 0xE7) {
+ usb_code = usb_code - 0xE0 + 0x80;
+ } else {
+ usb_code += KEY_HID_OFFSET;
+ }
+
+ if (state) {
+ _keyboard.press(usb_code);
+ } else {
+ _keyboard.release(usb_code);
+ }
+ }
+
+ bool isOffline() override {
+ return (USBComposite == false);
+ }
+
+ KeyboardLedsState getLeds() override {
+ uint8_t leds = _keyboard.getLEDs();
+ KeyboardLedsState result = {
+ .caps = leds & 0b00000010,
+ .scroll = leds & 0b00000100,
+ .num = leds & 0b00000001,
+ };
+ return result;
+ }
+
+ private:
+ HidWrapper& _hidWrapper;
+ HIDKeyboard _keyboard;
+ };
+}
diff --git a/hid/arduino/lib/drivers-stm32/usb/mouse-absolute-stm32.h b/hid/arduino/lib/drivers-stm32/usb/mouse-absolute-stm32.h
new file mode 100644
index 00000000..65195c78
--- /dev/null
+++ b/hid/arduino/lib/drivers-stm32/usb/mouse-absolute-stm32.h
@@ -0,0 +1,86 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#include <USBComposite.h>
+
+#include "mouse.h"
+#include "hid-wrapper-stm32.h"
+
+
+namespace DRIVERS {
+ const uint8_t reportDescriptionMouseAbsolute[] = {
+ HID_ABS_MOUSE_REPORT_DESCRIPTOR()
+ };
+
+ class UsbMouseAbsolute : public Mouse {
+ public:
+ UsbMouseAbsolute(HidWrapper& _hidWrapper) : Mouse(USB_MOUSE_ABSOLUTE),
+ _hidWrapper(_hidWrapper), _mouse(_hidWrapper.usbHid) {
+ _hidWrapper.addReportDescriptor(reportDescriptionMouseAbsolute, sizeof(reportDescriptionMouseAbsolute));
+ }
+
+ void begin() override {
+ _hidWrapper.begin();
+ }
+
+ void clear() override {
+ _mouse.release(0xFF);
+ }
+
+ void sendButtons (
+ bool left_select, bool left_state,
+ bool right_select, bool right_state,
+ bool middle_select, bool middle_state,
+ bool up_select, bool up_state,
+ bool down_select, bool down_state) override {
+
+# define SEND_BUTTON(x_low, x_up) { \
+ if (x_low##_select) { \
+ if (x_low##_state) _mouse.press(MOUSE_##x_up); \
+ else _mouse.release(MOUSE_##x_up); \
+ } \
+ }
+ SEND_BUTTON(left, LEFT);
+ SEND_BUTTON(right, RIGHT);
+ SEND_BUTTON(middle, MIDDLE);
+# undef SEND_BUTTON
+ }
+
+ void sendMove(int x, int y) override {
+ _mouse.move(x, y);
+ }
+
+ void sendWheel(int delta_y) override {
+ _mouse.move(0, 0, delta_y);
+ }
+
+ bool isOffline() override {
+ return (USBComposite == false);
+ }
+
+ private:
+ HidWrapper& _hidWrapper;
+ HIDAbsMouse _mouse;
+ };
+}
diff --git a/hid/arduino/lib/drivers-stm32/usb/mouse-relative-stm32.h b/hid/arduino/lib/drivers-stm32/usb/mouse-relative-stm32.h
new file mode 100644
index 00000000..4524e20b
--- /dev/null
+++ b/hid/arduino/lib/drivers-stm32/usb/mouse-relative-stm32.h
@@ -0,0 +1,86 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#include <USBComposite.h>
+
+#include "mouse.h"
+#include "hid-wrapper-stm32.h"
+
+
+namespace DRIVERS {
+ const uint8_t reportDescriptionMouseRelative[] = {
+ HID_MOUSE_REPORT_DESCRIPTOR()
+ };
+
+ class UsbMouseRelative : public Mouse {
+ public:
+ UsbMouseRelative(HidWrapper& _hidWrapper) : Mouse(USB_MOUSE_RELATIVE),
+ _hidWrapper(_hidWrapper), _mouse(_hidWrapper.usbHid) {
+ _hidWrapper.addReportDescriptor(reportDescriptionMouseRelative, sizeof(reportDescriptionMouseRelative));
+ }
+
+ void begin() override {
+ _hidWrapper.begin();
+ }
+
+ void clear() override {
+ _mouse.release(0xFF);
+ }
+
+ void sendButtons (
+ bool left_select, bool left_state,
+ bool right_select, bool right_state,
+ bool middle_select, bool middle_state,
+ bool up_select, bool up_state,
+ bool down_select, bool down_state) override {
+
+# define SEND_BUTTON(x_low, x_up) { \
+ if (x_low##_select) { \
+ if (x_low##_state) _mouse.press(MOUSE_##x_up); \
+ else _mouse.release(MOUSE_##x_up); \
+ } \
+ }
+ SEND_BUTTON(left, LEFT);
+ SEND_BUTTON(right, RIGHT);
+ SEND_BUTTON(middle, MIDDLE);
+# undef SEND_BUTTON
+ }
+
+ void sendRelative(int x, int y) override {
+ _mouse.move(x, y);
+ }
+
+ void sendWheel(int delta_y) override {
+ _mouse.move(0, 0, delta_y);
+ }
+
+ bool isOffline() override {
+ return (USBComposite == false);
+ }
+
+ private:
+ HidWrapper& _hidWrapper;
+ HIDMouse _mouse;
+ };
+}
diff --git a/hid/arduino/lib/drivers/aum.h b/hid/arduino/lib/drivers/aum.h
new file mode 100644
index 00000000..3d407d12
--- /dev/null
+++ b/hid/arduino/lib/drivers/aum.h
@@ -0,0 +1,48 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#include <digitalWriteFast.h>
+
+
+inline void aumInit() {
+ pinModeFast(AUM_IS_USB_POWERED_PIN, INPUT);
+ pinModeFast(AUM_SET_USB_VBUS_PIN, OUTPUT);
+ pinModeFast(AUM_SET_USB_CONNECTED_PIN, OUTPUT);
+ digitalWriteFast(AUM_SET_USB_CONNECTED_PIN, HIGH);
+}
+
+inline void aumProxyUsbVbus() {
+ bool vbus = digitalReadFast(AUM_IS_USB_POWERED_PIN);
+ if (digitalReadFast(AUM_SET_USB_VBUS_PIN) != vbus) {
+ digitalWriteFast(AUM_SET_USB_VBUS_PIN, vbus);
+ }
+}
+
+inline void aumSetUsbConnected(bool connected) {
+ digitalWriteFast(AUM_SET_USB_CONNECTED_PIN, connected);
+}
+
+inline bool aumIsUsbConnected() {
+ return digitalReadFast(AUM_SET_USB_CONNECTED_PIN);
+}
diff --git a/hid/arduino/lib/drivers/board.h b/hid/arduino/lib/drivers/board.h
new file mode 100644
index 00000000..cc431d62
--- /dev/null
+++ b/hid/arduino/lib/drivers/board.h
@@ -0,0 +1,41 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#include "driver.h"
+
+
+namespace DRIVERS {
+ enum status {
+ RX_DATA = 0,
+ KEYBOARD_ONLINE,
+ MOUSE_ONLINE,
+ };
+
+ struct Board : public Driver {
+ using Driver::Driver;
+ virtual void reset() {}
+ virtual void periodic() {}
+ virtual void updateStatus(status status) {}
+ };
+}
diff --git a/hid/arduino/lib/drivers/connection.h b/hid/arduino/lib/drivers/connection.h
new file mode 100644
index 00000000..7a9beb7b
--- /dev/null
+++ b/hid/arduino/lib/drivers/connection.h
@@ -0,0 +1,54 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#include "driver.h"
+#include "stdint.h"
+
+
+namespace DRIVERS {
+ typedef void (*DataHandler)(const uint8_t *data, size_t size);
+ typedef void (*TimeoutHandler)();
+
+ struct Connection : public Driver {
+ using Driver::Driver;
+
+ virtual void begin() {}
+
+ virtual void periodic() {}
+
+ void onTimeout(TimeoutHandler cb) {
+ _timeout_cb = cb;
+ }
+
+ void onData(DataHandler cb) {
+ _data_cb = cb;
+ }
+
+ virtual void write(const uint8_t *data, size_t size) = 0;
+
+ protected:
+ TimeoutHandler _timeout_cb = nullptr;
+ DataHandler _data_cb = nullptr;
+ };
+}
diff --git a/hid/arduino/lib/drivers/driver.h b/hid/arduino/lib/drivers/driver.h
new file mode 100644
index 00000000..af60b112
--- /dev/null
+++ b/hid/arduino/lib/drivers/driver.h
@@ -0,0 +1,49 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#include <stdint.h>
+
+
+namespace DRIVERS {
+ enum type {
+ DUMMY = 0,
+ USB_MOUSE_ABSOLUTE,
+ USB_MOUSE_RELATIVE,
+ USB_MOUSE_ABSOLUTE_WIN98,
+ USB_KEYBOARD,
+ PS2_KEYBOARD,
+ NON_VOLATILE_STORAGE,
+ BOARD,
+ CONNECTION,
+ };
+
+ class Driver {
+ public:
+ Driver(type _type) : _type(_type) {}
+ uint8_t getType() { return _type; }
+
+ private:
+ type _type;
+ };
+}
diff --git a/hid/arduino/lib/drivers/factory.h b/hid/arduino/lib/drivers/factory.h
new file mode 100644
index 00000000..116a6c84
--- /dev/null
+++ b/hid/arduino/lib/drivers/factory.h
@@ -0,0 +1,39 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+#pragma once
+
+#include "keyboard.h"
+#include "mouse.h"
+#include "storage.h"
+#include "board.h"
+#include "connection.h"
+
+
+namespace DRIVERS {
+ struct Factory {
+ static Keyboard *makeKeyboard(type _type);
+ static Mouse *makeMouse(type _type);
+ static Storage *makeStorage(type _type);
+ static Board *makeBoard(type _type);
+ static Connection *makeConnection(type _type);
+ };
+}
diff --git a/hid/arduino/lib/drivers/keyboard.h b/hid/arduino/lib/drivers/keyboard.h
new file mode 100644
index 00000000..1128def9
--- /dev/null
+++ b/hid/arduino/lib/drivers/keyboard.h
@@ -0,0 +1,68 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#include <stdint.h>
+
+#include "driver.h"
+
+
+namespace DRIVERS {
+ typedef struct {
+ bool caps;
+ bool scroll;
+ bool num;
+ } KeyboardLedsState;
+
+ struct Keyboard : public Driver {
+ using Driver::Driver;
+
+ virtual void begin() {}
+
+ /**
+ * Release all keys
+ */
+ virtual void clear() {}
+
+ /**
+ * Sends key
+ * @param code ???
+ * @param state true pressed, false released
+ */
+ virtual void sendKey(uint8_t code, bool state) {}
+
+ virtual void periodic() {}
+
+ /**
+ * False if online or unknown. Otherwise true.
+ */
+ virtual bool isOffline() {
+ return false;
+ }
+
+ virtual KeyboardLedsState getLeds() {
+ KeyboardLedsState result = {0};
+ return result;
+ }
+ };
+}
diff --git a/hid/arduino/lib/drivers/mouse.h b/hid/arduino/lib/drivers/mouse.h
new file mode 100644
index 00000000..83216e29
--- /dev/null
+++ b/hid/arduino/lib/drivers/mouse.h
@@ -0,0 +1,51 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#include <stdint.h>
+
+#include "driver.h"
+
+
+namespace DRIVERS {
+ struct Mouse : public Driver {
+ using Driver::Driver;
+ virtual void begin() {}
+
+ /**
+ * Release all keys
+ */
+ virtual void clear() {}
+ virtual void sendButtons(
+ bool left_select, bool left_state,
+ bool right_select, bool right_state,
+ bool middle_select, bool middle_state,
+ bool up_select, bool up_state,
+ bool down_select, bool down_state) {}
+ virtual void sendMove(int x, int y) {}
+ virtual void sendRelative(int x, int y) {}
+ virtual void sendWheel(int delta_y) {}
+ virtual bool isOffline() { return false; }
+ virtual void periodic() {}
+ };
+}
diff --git a/hid/arduino/lib/drivers/serial.h b/hid/arduino/lib/drivers/serial.h
new file mode 100644
index 00000000..32ec5613
--- /dev/null
+++ b/hid/arduino/lib/drivers/serial.h
@@ -0,0 +1,68 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#ifdef CMD_SERIAL
+#include "connection.h"
+
+
+namespace DRIVERS {
+#ifdef Serial
+# undef Serial
+#endif
+ struct Serial : public Connection {
+ Serial() : Connection(CONNECTION) {}
+
+ void begin() override {
+ CMD_SERIAL.begin(CMD_SERIAL_SPEED);
+ }
+
+ void periodic() override {
+ if (CMD_SERIAL.available() > 0) {
+ _buffer[_index] = (uint8_t)CMD_SERIAL.read();
+ if (_index == 7) {
+ _data_cb(_buffer, 8);
+ _index = 0;
+ } else {
+ _last = micros();
+ ++_index;
+ }
+ } else if (_index > 0) {
+ if (is_micros_timed_out(_last, CMD_SERIAL_TIMEOUT)) {
+ _timeout_cb();
+ _index = 0;
+ }
+ }
+ }
+
+ void write(const uint8_t *data, size_t size) override {
+ CMD_SERIAL.write(data, size);
+ }
+
+ private:
+ unsigned long _last = 0;
+ uint8_t _index = 0;
+ uint8_t _buffer[8];
+ };
+}
+#endif
diff --git a/hid/arduino/lib/drivers/storage.h b/hid/arduino/lib/drivers/storage.h
new file mode 100644
index 00000000..3b676905
--- /dev/null
+++ b/hid/arduino/lib/drivers/storage.h
@@ -0,0 +1,35 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#include "driver.h"
+#include "stdlib.h"
+
+
+namespace DRIVERS {
+ struct Storage : public Driver {
+ using Driver::Driver;
+ virtual void readBlock(void *dest, const void *src, size_t size) {}
+ virtual void updateBlock(const void *src, void *dest, size_t size) {}
+ };
+}
diff --git a/hid/arduino/lib/drivers/tools.cpp b/hid/arduino/lib/drivers/tools.cpp
new file mode 100644
index 00000000..a6585245
--- /dev/null
+++ b/hid/arduino/lib/drivers/tools.cpp
@@ -0,0 +1,32 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#include "tools.h"
+
+
+bool is_micros_timed_out(unsigned long start_ts, unsigned long timeout) {
+ unsigned long now = micros();
+ return (
+ (now >= start_ts && now - start_ts > timeout)
+ || (now < start_ts && ((unsigned long)-1) - start_ts + now > timeout)
+ );
+}
diff --git a/hid/arduino/lib/drivers/tools.h b/hid/arduino/lib/drivers/tools.h
new file mode 100644
index 00000000..34f88022
--- /dev/null
+++ b/hid/arduino/lib/drivers/tools.h
@@ -0,0 +1,28 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+#include <Arduino.h>
+
+
+bool is_micros_timed_out(unsigned long start_ts, unsigned long timeout);
diff --git a/hid/arduino/lib/drivers/usb-keymap.h b/hid/arduino/lib/drivers/usb-keymap.h
new file mode 100644
index 00000000..5a86337f
--- /dev/null
+++ b/hid/arduino/lib/drivers/usb-keymap.h
@@ -0,0 +1,141 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+
+uint8_t keymapUsb(uint8_t code) {
+ switch (code) {
+ case 1: return 4; // KeyA
+ case 2: return 5; // KeyB
+ case 3: return 6; // KeyC
+ case 4: return 7; // KeyD
+ case 5: return 8; // KeyE
+ case 6: return 9; // KeyF
+ case 7: return 10; // KeyG
+ case 8: return 11; // KeyH
+ case 9: return 12; // KeyI
+ case 10: return 13; // KeyJ
+ case 11: return 14; // KeyK
+ case 12: return 15; // KeyL
+ case 13: return 16; // KeyM
+ case 14: return 17; // KeyN
+ case 15: return 18; // KeyO
+ case 16: return 19; // KeyP
+ case 17: return 20; // KeyQ
+ case 18: return 21; // KeyR
+ case 19: return 22; // KeyS
+ case 20: return 23; // KeyT
+ case 21: return 24; // KeyU
+ case 22: return 25; // KeyV
+ case 23: return 26; // KeyW
+ case 24: return 27; // KeyX
+ case 25: return 28; // KeyY
+ case 26: return 29; // KeyZ
+ case 27: return 30; // Digit1
+ case 28: return 31; // Digit2
+ case 29: return 32; // Digit3
+ case 30: return 33; // Digit4
+ case 31: return 34; // Digit5
+ case 32: return 35; // Digit6
+ case 33: return 36; // Digit7
+ case 34: return 37; // Digit8
+ case 35: return 38; // Digit9
+ case 36: return 39; // Digit0
+ case 37: return 40; // Enter
+ case 38: return 41; // Escape
+ case 39: return 42; // Backspace
+ case 40: return 43; // Tab
+ case 41: return 44; // Space
+ case 42: return 45; // Minus
+ case 43: return 46; // Equal
+ case 44: return 47; // BracketLeft
+ case 45: return 48; // BracketRight
+ case 46: return 49; // Backslash
+ case 47: return 51; // Semicolon
+ case 48: return 52; // Quote
+ case 49: return 53; // Backquote
+ case 50: return 54; // Comma
+ case 51: return 55; // Period
+ case 52: return 56; // Slash
+ case 53: return 57; // CapsLock
+ case 54: return 58; // F1
+ case 55: return 59; // F2
+ case 56: return 60; // F3
+ case 57: return 61; // F4
+ case 58: return 62; // F5
+ case 59: return 63; // F6
+ case 60: return 64; // F7
+ case 61: return 65; // F8
+ case 62: return 66; // F9
+ case 63: return 67; // F10
+ case 64: return 68; // F11
+ case 65: return 69; // F12
+ case 66: return 70; // PrintScreen
+ case 67: return 73; // Insert
+ case 68: return 74; // Home
+ case 69: return 75; // PageUp
+ case 70: return 76; // Delete
+ case 71: return 77; // End
+ case 72: return 78; // PageDown
+ case 73: return 79; // ArrowRight
+ case 74: return 80; // ArrowLeft
+ case 75: return 81; // ArrowDown
+ case 76: return 82; // ArrowUp
+ case 77: return 224; // ControlLeft
+ case 78: return 225; // ShiftLeft
+ case 79: return 226; // AltLeft
+ case 80: return 227; // MetaLeft
+ case 81: return 228; // ControlRight
+ case 82: return 229; // ShiftRight
+ case 83: return 230; // AltRight
+ case 84: return 231; // MetaRight
+ case 85: return 72; // Pause
+ case 86: return 71; // ScrollLock
+ case 87: return 83; // NumLock
+ case 88: return 101; // ContextMenu
+ case 89: return 84; // NumpadDivide
+ case 90: return 85; // NumpadMultiply
+ case 91: return 86; // NumpadSubtract
+ case 92: return 87; // NumpadAdd
+ case 93: return 88; // NumpadEnter
+ case 94: return 89; // Numpad1
+ case 95: return 90; // Numpad2
+ case 96: return 91; // Numpad3
+ case 97: return 92; // Numpad4
+ case 98: return 93; // Numpad5
+ case 99: return 94; // Numpad6
+ case 100: return 95; // Numpad7
+ case 101: return 96; // Numpad8
+ case 102: return 97; // Numpad9
+ case 103: return 98; // Numpad0
+ case 104: return 99; // NumpadDecimal
+ case 105: return 102; // Power
+ case 106: return 100; // IntlBackslash
+ case 107: return 137; // IntlYen
+ case 108: return 135; // IntlRo
+ case 109: return 136; // KanaMode
+ case 110: return 138; // Convert
+ case 111: return 139; // NonConvert
+ default: return 0;
+ }
+}
diff --git a/hid/arduino/lib/drivers/usb-keymap.h.mako b/hid/arduino/lib/drivers/usb-keymap.h.mako
new file mode 100644
index 00000000..5ae01421
--- /dev/null
+++ b/hid/arduino/lib/drivers/usb-keymap.h.mako
@@ -0,0 +1,37 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+<%! import operator %>
+uint8_t keymapUsb(uint8_t code) {
+ switch (code) {
+% for km in sorted(keymap, key=operator.attrgetter("mcu_code")):
+ % if km.usb_key.is_modifier:
+ case ${km.mcu_code}: return ${km.usb_key.arduino_modifier_code}; // ${km.web_name}
+ % else:
+ case ${km.mcu_code}: return ${km.usb_key.code}; // ${km.web_name}
+ % endif
+% endfor
+ default: return 0;
+ }
+}
diff --git a/hid/arduino/patch.py b/hid/arduino/patch.py
new file mode 100644
index 00000000..c7674759
--- /dev/null
+++ b/hid/arduino/patch.py
@@ -0,0 +1,49 @@
+# https://docs.platformio.org/en/latest/projectconf/advanced_scripting.html
+
+
+from os.path import exists
+from os.path import join
+from os.path import basename
+
+from typing import Dict
+
+Import("env")
+
+
+# =====
+def _get_pkg_path(name: str) -> str:
+ path = env.PioPlatform().get_package_dir(name)
+ assert exists(path)
+ return path
+
+
+def _get_libs() -> Dict[str, str]:
+ return {
+ builder.name: builder.path
+ for builder in env.GetLibBuilders()
+ }
+
+
+def _patch(path: str, patch_path: str) -> None:
+ assert exists(path)
+ flag_path: str = join(path, f".{basename(patch_path)}.done")
+ if not exists(flag_path):
+ # TODO check for failure
+ env.Execute(f"patch -p1 -d {path} < {patch_path}")
+ env.Execute(lambda *_, **__: open(flag_path, "w").close())
+
+
+# =====
+if env.GetProjectOption("platform") == "ststm32":
+ _patch(_get_pkg_path("framework-arduinoststm32-maple"), "patches/platformio-stm32f1-no-serial-usb.patch")
+elif env.GetProjectOption("platform") == "atmelavr":
+ _patch(_get_pkg_path("framework-arduino-avr"), "patches/arduino-main-no-usb.patch")
+ _patch(_get_pkg_path("framework-arduino-avr"), "patches/arduino-optional-cdc.patch")
+ _patch(_get_pkg_path("framework-arduino-avr"), "patches/arduino-get-plugged-endpoint.patch")
+
+ _libs = _get_libs()
+ _patch(_libs["HID-Project"], "patches/hid-shut-up.patch")
+ _patch(_libs["HID-Project"], "patches/hid-no-singletones.patch")
+ _patch(_libs["HID-Project"], "patches/hid-win98.patch")
+else:
+ assert(False)
diff --git a/hid/arduino/patches/arduino-get-plugged-endpoint.patch b/hid/arduino/patches/arduino-get-plugged-endpoint.patch
new file mode 100644
index 00000000..c32131ff
--- /dev/null
+++ b/hid/arduino/patches/arduino-get-plugged-endpoint.patch
@@ -0,0 +1,11 @@
+--- a/cores/arduino/PluggableUSB.h 2019-05-16 15:52:01.000000000 +0300
++++ b/cores/arduino/PluggableUSB.h 2020-11-14 20:57:30.942432544 +0300
+@@ -31,6 +31,8 @@
+ numEndpoints(numEps), numInterfaces(numIfs), endpointType(epType)
+ { }
+
++ uint8_t getPluggedEndpoint() { return pluggedEndpoint; }
++
+ protected:
+ virtual bool setup(USBSetup& setup) = 0;
+ virtual int getInterface(uint8_t* interfaceCount) = 0;
diff --git a/hid/arduino/patches/arduino-main-no-usb.patch b/hid/arduino/patches/arduino-main-no-usb.patch
new file mode 100644
index 00000000..ab10c9a8
--- /dev/null
+++ b/hid/arduino/patches/arduino-main-no-usb.patch
@@ -0,0 +1,24 @@
+diff --git a/cores/arduino/main.cpp b/cores/arduino/main.cpp
+index 434cd40..7aba76f 100644
+--- a/cores/arduino/main.cpp
++++ b/cores/arduino/main.cpp
+@@ -36,15 +36,15 @@ int main(void)
+
+ initVariant();
+
+-#if defined(USBCON)
+- USBDevice.attach();
+-#endif
++// #if defined(USBCON)
++// USBDevice.attach();
++// #endif
+
+ setup();
+
+ for (;;) {
+ loop();
+- if (serialEventRun) serialEventRun();
++ // if (serialEventRun) serialEventRun();
+ }
+
+ return 0;
diff --git a/hid/arduino/patches/arduino-optional-cdc.patch b/hid/arduino/patches/arduino-optional-cdc.patch
new file mode 100644
index 00000000..08d8c078
--- /dev/null
+++ b/hid/arduino/patches/arduino-optional-cdc.patch
@@ -0,0 +1,141 @@
+From 8e823d276f939d79b2d323fad675fb8442a718c2 Mon Sep 17 00:00:00 2001
+From: Daniel Gibson <[email protected]>
+Date: Tue, 5 Jan 2021 13:48:43 +0100
+Subject: [PATCH] Allow disabling CDC with -DCDC_DISABLED
+
+https://github.com/arduino/ArduinoCore-avr/pull/383
+
+Sometimes Arduino-based USB devices don't work because some hardware
+(like KVM switches) gets confused by the CDC sub-devices.
+This change makes it relatively easy to disable CDC at compiletime.
+Disabling it of course means that the serial console won't work anymore,
+so you need to use the reset button when flashing.
+
+CDC_DISABLED is also used in ArduinoCore-samd for the same purpose.
+
+based on
+https://github.com/gdsports/usb-metamorph/tree/master/USBSerPassThruLine
+
+See also https://github.com/NicoHood/HID/issues/225 and
+https://github.com/arduino/Arduino/issues/6387 and
+https://forum.arduino.cc/index.php?topic=545288.msg3717028#msg3717028
+---
+ cores/arduino/CDC.cpp | 8 ++++++++
+ cores/arduino/USBCore.cpp | 18 +++++++++++++++++-
+ cores/arduino/USBDesc.h | 17 +++++++++++++++++
+ 3 files changed, 42 insertions(+), 1 deletion(-)
+
+diff --git a/cores/arduino/CDC.cpp b/cores/arduino/CDC.cpp
+index 4ff6b9b4..7d5afaab 100644
+--- a/cores/arduino/CDC.cpp
++++ b/cores/arduino/CDC.cpp
+@@ -22,6 +22,13 @@
+
+ #if defined(USBCON)
+
++#ifndef CDC_ENABLED
++
++#warning "! Disabled serial console via USB (CDC)!"
++#warning "! With this change you'll have to use the Arduino's reset button/pin to flash (upload)!"
++
++#else // CDC not disabled
++
+ typedef struct
+ {
+ u32 dwDTERate;
+@@ -299,4 +306,5 @@ int32_t Serial_::readBreak() {
+
+ Serial_ Serial;
+
++#endif /* if defined(CDC_ENABLED) */
+ #endif /* if defined(USBCON) */
+diff --git a/cores/arduino/USBCore.cpp b/cores/arduino/USBCore.cpp
+index dc6bc387..93352387 100644
+--- a/cores/arduino/USBCore.cpp
++++ b/cores/arduino/USBCore.cpp
+@@ -69,8 +69,18 @@ const u8 STRING_MANUFACTURER[] PROGMEM = USB_MANUFACTURER;
+ #define DEVICE_CLASS 0x02
+
+ // DEVICE DESCRIPTOR
++
++#ifdef CDC_ENABLED
+ const DeviceDescriptor USB_DeviceDescriptorIAD =
+ D_DEVICE(0xEF,0x02,0x01,64,USB_VID,USB_PID,0x100,IMANUFACTURER,IPRODUCT,ISERIAL,1);
++#else // CDC_DISABLED
++// The default descriptor uses USB class OxEF, subclass 0x02 with protocol 1
++// which means "Interface Association Descriptor" - that's needed for the CDC,
++// but doesn't make much sense as a default for custom devices when CDC is disabled.
++// (0x00 means "Use class information in the Interface Descriptors" which should be generally ok)
++const DeviceDescriptor USB_DeviceDescriptorIAD =
++ D_DEVICE(0x00,0x00,0x00,64,USB_VID,USB_PID,0x100,IMANUFACTURER,IPRODUCT,ISERIAL,1);
++#endif
+
+ //==================================================================
+ //==================================================================
+@@ -328,10 +338,12 @@ int USB_Send(u8 ep, const void* d, int len)
+ u8 _initEndpoints[USB_ENDPOINTS] =
+ {
+ 0, // Control Endpoint
+-
++
++#ifdef CDC_ENABLED
+ EP_TYPE_INTERRUPT_IN, // CDC_ENDPOINT_ACM
+ EP_TYPE_BULK_OUT, // CDC_ENDPOINT_OUT
+ EP_TYPE_BULK_IN, // CDC_ENDPOINT_IN
++#endif
+
+ // Following endpoints are automatically initialized to 0
+ };
+@@ -373,10 +385,12 @@ void InitEndpoints()
+ static
+ bool ClassInterfaceRequest(USBSetup& setup)
+ {
++#ifdef CDC_ENABLED
+ u8 i = setup.wIndex;
+
+ if (CDC_ACM_INTERFACE == i)
+ return CDC_Setup(setup);
++#endif
+
+ #ifdef PLUGGABLE_USB_ENABLED
+ return PluggableUSB().setup(setup);
+@@ -466,7 +480,9 @@ static u8 SendInterfaces()
+ {
+ u8 interfaces = 0;
+
++#ifdef CDC_ENABLED
+ CDC_GetInterface(&interfaces);
++#endif
+
+ #ifdef PLUGGABLE_USB_ENABLED
+ PluggableUSB().getInterface(&interfaces);
+diff --git a/cores/arduino/USBDesc.h b/cores/arduino/USBDesc.h
+index c0dce079..b55ac20b 100644
+--- a/cores/arduino/USBDesc.h
++++ b/cores/arduino/USBDesc.h
+@@ -26,8 +26,25 @@
+
+ #define ISERIAL_MAX_LEN 20
+
++// Uncomment the following line or pass -DCDC_DISABLED to the compiler
++// to disable CDC (serial console via USB).
++// That's useful if you want to create an USB device (like an USB Boot Keyboard)
++// that works even with problematic devices (like KVM switches).
++// Keep in mind that with this change you'll have to use the Arduino's
++// reset button to be able to flash it.
++//#define CDC_DISABLED
++
++#ifndef CDC_DISABLED
++#define CDC_ENABLED
++#endif
++
++#ifdef CDC_ENABLED
+ #define CDC_INTERFACE_COUNT 2
+ #define CDC_ENPOINT_COUNT 3
++#else // CDC_DISABLED
++#define CDC_INTERFACE_COUNT 0
++#define CDC_ENPOINT_COUNT 0
++#endif
+
+ #define CDC_ACM_INTERFACE 0 // CDC ACM
+ #define CDC_DATA_INTERFACE 1 // CDC Data
diff --git a/hid/arduino/patches/hid-no-singletones.patch b/hid/arduino/patches/hid-no-singletones.patch
new file mode 100644
index 00000000..85298448
--- /dev/null
+++ b/hid/arduino/patches/hid-no-singletones.patch
@@ -0,0 +1,66 @@
+diff -u -r a/src/SingleReport/BootKeyboard.cpp b/src/SingleReport/BootKeyboard.cpp
+--- a/src/SingleReport/BootKeyboard.cpp 2019-07-13 21:16:23.000000000 +0300
++++ b/src/SingleReport/BootKeyboard.cpp 2020-11-17 18:59:36.618815374 +0300
+@@ -206,6 +206,6 @@
+ }
+
+
+-BootKeyboard_ BootKeyboard;
++//BootKeyboard_ BootKeyboard;
+
+
+diff -u -r a/src/SingleReport/BootKeyboard.h b/src/SingleReport/BootKeyboard.h
+--- a/src/SingleReport/BootKeyboard.h 2019-07-13 21:16:23.000000000 +0300
++++ b/src/SingleReport/BootKeyboard.h 2020-11-17 19:00:54.967113649 +0300
+@@ -80,6 +80,6 @@
+ uint8_t* featureReport;
+ int featureLength;
+ };
+-extern BootKeyboard_ BootKeyboard;
++//extern BootKeyboard_ BootKeyboard;
+
+
+diff -u -r a/src/SingleReport/BootMouse.cpp b/src/SingleReport/BootMouse.cpp
+--- a/src/SingleReport/BootMouse.cpp 2019-07-13 21:16:23.000000000 +0300
++++ b/src/SingleReport/BootMouse.cpp 2020-11-17 18:59:22.859113905 +0300
+@@ -139,6 +139,6 @@
+ }
+ }
+
+-BootMouse_ BootMouse;
++//BootMouse_ BootMouse;
+
+
+diff -u -r a/src/SingleReport/BootMouse.h b/src/SingleReport/BootMouse.h
+--- a/src/SingleReport/BootMouse.h 2019-07-13 21:16:23.000000000 +0300
++++ b/src/SingleReport/BootMouse.h 2020-11-17 19:01:04.076915591 +0300
+@@ -48,6 +48,6 @@
+
+ virtual void SendReport(void* data, int length) override;
+ };
+-extern BootMouse_ BootMouse;
++//extern BootMouse_ BootMouse;
+
+
+diff -u -r a/src/SingleReport/SingleAbsoluteMouse.cpp b/src/SingleReport/SingleAbsoluteMouse.cpp
+--- a/src/SingleReport/SingleAbsoluteMouse.cpp 2020-11-17 18:39:35.314843889 +0300
++++ b/src/SingleReport/SingleAbsoluteMouse.cpp 2020-11-17 18:59:12.189345326 +0300
+@@ -139,6 +139,6 @@
+ USB_Send(pluggedEndpoint | TRANSFER_RELEASE, data, length);
+ }
+
+-SingleAbsoluteMouse_ SingleAbsoluteMouse;
++//SingleAbsoluteMouse_ SingleAbsoluteMouse;
+
+
+diff -u -r a/src/SingleReport/SingleAbsoluteMouse.h b/src/SingleReport/SingleAbsoluteMouse.h
+--- a/src/SingleReport/SingleAbsoluteMouse.h 2019-07-13 21:16:23.000000000 +0300
++++ b/src/SingleReport/SingleAbsoluteMouse.h 2020-11-17 19:01:21.356539808 +0300
+@@ -49,6 +49,6 @@
+
+ virtual inline void SendReport(void* data, int length) override;
+ };
+-extern SingleAbsoluteMouse_ SingleAbsoluteMouse;
++//extern SingleAbsoluteMouse_ SingleAbsoluteMouse;
+
+
diff --git a/hid/arduino/patches/hid-shut-up.patch b/hid/arduino/patches/hid-shut-up.patch
new file mode 100644
index 00000000..1de9defb
--- /dev/null
+++ b/hid/arduino/patches/hid-shut-up.patch
@@ -0,0 +1,16 @@
+diff --git a/src/KeyboardLayouts/ImprovedKeylayouts.h b/src/KeyboardLayouts/ImprovedKeylayouts.h
+index 03b92a0..ee0bab4 100644
+--- a/src/KeyboardLayouts/ImprovedKeylayouts.h
++++ b/src/KeyboardLayouts/ImprovedKeylayouts.h
+@@ -51,9 +51,9 @@ enum KeyboardLeds : uint8_t {
+ #ifndef HID_CUSTOM_LAYOUT
+ #define HID_CUSTOM_LAYOUT
+ #define LAYOUT_US_ENGLISH
+- #pragma message "Using default ASCII layout for keyboard modules"
++ //#pragma message "Using default ASCII layout for keyboard modules"
+ #else
+- #pragma message "Using custom layout for keyboard modules"
++ //#pragma message "Using custom layout for keyboard modules"
+ #endif
+
+ // Hut1_12v2.pdf
diff --git a/hid/arduino/patches/hid-win98.patch b/hid/arduino/patches/hid-win98.patch
new file mode 100644
index 00000000..29670720
--- /dev/null
+++ b/hid/arduino/patches/hid-win98.patch
@@ -0,0 +1,82 @@
+From 82c8aee0ae3739eb22ae11b8f527d0c50b0ced31 Mon Sep 17 00:00:00 2001
+From: Maxim Devaev <[email protected]>
+Date: Sat, 14 Aug 2021 10:10:06 +0300
+Subject: [PATCH] Fixed absolute mouse positioning in Windows 98
+
+Windows 98 contains a buggy HID driver. It allows you to move only
+on the upper right quarter of the screen. Yes, this is indeed exceeding
+the maximum value from the HID report. Yes, it really should work that way.
+
+VirtualBox has a similar fix, but with a shift in the other direction
+(I didn't dig the details). I suppose it can be fixed somehow with a special
+HID descriptor, but the proposed fix is simpler and really works.
+
+Related: https://github.com/pikvm/pikvm/issues/159
+---
+ src/HID-APIs/AbsoluteMouseAPI.h | 3 +++
+ src/HID-APIs/AbsoluteMouseAPI.hpp | 17 ++++++++++++++++-
+ 2 files changed, 19 insertions(+), 1 deletion(-)
+
+diff --git a/src/HID-APIs/AbsoluteMouseAPI.h b/src/HID-APIs/AbsoluteMouseAPI.h
+index 66dbe42..c99e6a5 100644
+--- a/src/HID-APIs/AbsoluteMouseAPI.h
++++ b/src/HID-APIs/AbsoluteMouseAPI.h
+@@ -57,6 +57,7 @@ class AbsoluteMouseAPI
+ int16_t xAxis;
+ int16_t yAxis;
+ uint8_t _buttons;
++ bool win98_fix;
+ inline void buttons(uint8_t b);
+
+ inline int16_t qadd16(int16_t base, int16_t increment);
+@@ -65,6 +66,8 @@ class AbsoluteMouseAPI
+ inline AbsoluteMouseAPI(void);
+ inline void begin(void);
+ inline void end(void);
++ inline void setWin98FixEnabled(bool enabled);
++ inline bool isWin98FixEnabled(void);
+
+ inline void click(uint8_t b = MOUSE_LEFT);
+ inline void moveTo(int x, int y, signed char wheel = 0);
+diff --git a/src/HID-APIs/AbsoluteMouseAPI.hpp b/src/HID-APIs/AbsoluteMouseAPI.hpp
+index 0913c8a..0b9aaa2 100644
+--- a/src/HID-APIs/AbsoluteMouseAPI.hpp
++++ b/src/HID-APIs/AbsoluteMouseAPI.hpp
+@@ -51,7 +51,7 @@ int16_t AbsoluteMouseAPI::qadd16(int16_t base, int16_t increment) {
+ }
+
+ AbsoluteMouseAPI::AbsoluteMouseAPI(void):
+-xAxis(0), yAxis(0), _buttons(0)
++xAxis(0), yAxis(0), _buttons(0), win98_fix(false)
+ {
+ // Empty
+ }
+@@ -66,6 +66,14 @@ void AbsoluteMouseAPI::end(void){
+ moveTo(xAxis, yAxis, 0);
+ }
+
++void AbsoluteMouseAPI::setWin98FixEnabled(bool enabled){
++ win98_fix = enabled;
++}
++
++bool AbsoluteMouseAPI::isWin98FixEnabled(void){
++ return win98_fix;
++}
++
+ void AbsoluteMouseAPI::click(uint8_t b){
+ _buttons = b;
+ moveTo(xAxis, yAxis, 0);
+@@ -84,6 +92,13 @@ void AbsoluteMouseAPI::moveTo(int x, int y, signed char wheel){
+ // See detauls in AbsoluteMouse sources and here: https://github.com/NicoHood/HID/pull/306
+ report.xAxis = ((int32_t)x + 32768) / 2;
+ report.yAxis = ((int32_t)y + 32768) / 2;
++ if (win98_fix) {
++ // Windows 98 contains a buggy driver.
++ // Yes, this is indeed exceeding the maximum value from the HID report.
++ // Yes, it really should work that way.
++ report.xAxis <<= 1;
++ report.yAxis <<= 1;
++ }
+ report.wheel = wheel;
+ SendReport(&report, sizeof(report));
+ }
diff --git a/hid/arduino/patches/platformio-stm32f1-no-serial-usb.patch b/hid/arduino/patches/platformio-stm32f1-no-serial-usb.patch
new file mode 100644
index 00000000..547db168
--- /dev/null
+++ b/hid/arduino/patches/platformio-stm32f1-no-serial-usb.patch
@@ -0,0 +1,11 @@
+--- aaa/tools/platformio-build-stm32f1.py 2022-07-16 18:54:42.536695468 +0200
++++ bbb/tools/platformio-build-stm32f1.py 2022-07-16 19:03:10.988056751 +0200
+@@ -121,7 +121,7 @@
+ env.Append(
+ CPPDEFINES=[
+ ("CONFIG_MAPLE_MINI_NO_DISABLE_DEBUG", 1),
+- "SERIAL_USB"
++ # "SERIAL_USB"
+ ])
+
+ is_generic = board.startswith("generic") or board == "hytiny_stm32f103t"
diff --git a/hid/arduino/platformio-avr.ini b/hid/arduino/platformio-avr.ini
new file mode 100644
index 00000000..8484ef50
--- /dev/null
+++ b/hid/arduino/platformio-avr.ini
@@ -0,0 +1,119 @@
+# http://docs.platformio.org/page/projectconf.html
+[platformio]
+core_dir = ./.platformio/
+
+[env]
+platform = atmelavr
+board = micro
+framework = arduino
+lib_deps =
+ git+https://github.com/NicoHood/HID#2.8.2
+ git+https://github.com/Harvie/ps2dev#v0.0.3
+ drivers-avr
+extra_scripts =
+ pre:avrdude.py
+ post:patch.py
+platform_packages =
+ tool-avrdude
+
+[_common]
+build_flags =
+ -DHID_USB_CHECK_ENDPOINT
+# ----- The default config with dynamic switching -----
+ -DHID_DYNAMIC
+ -DHID_WITH_USB
+ -DHID_SET_USB_KBD
+ -DHID_SET_USB_MOUSE_ABS
+# ----- The USB ABS fix for Windows 98 (https://github.com/pikvm/pikvm/issues/159) -----
+# -DHID_WITH_USB_WIN98
+# ----- PS2 keyboard only -----
+# -DHID_WITH_PS2
+# -DHID_SET_PS2_KBD
+# ----- PS2 keyboard + USB absolute mouse -----
+# -DHID_WITH_USB
+# -DHID_WITH_PS2
+# -DHID_SET_PS2_KBD
+# -DHID_SET_USB_MOUSE_ABS
+# ----- PS2 keyboard + USB relative mouse -----
+# -DHID_WITH_USB
+# -DHID_WITH_PS2
+# -DHID_SET_PS2_KBD
+# -DHID_SET_USB_MOUSE_REL
+
+[_non_aum_pinout] =
+build_flags =
+ -DHID_PS2_KBD_CLOCK_PIN=7
+ -DHID_PS2_KBD_DATA_PIN=5
+
+
+# ===== Serial =====
+[env:serial]
+extends =
+ _common
+ _non_aum_pinout
+build_flags =
+ ${_common.build_flags}
+ ${_non_aum_pinout.build_flags}
+ -DCMD_SERIAL=Serial1
+ -DCMD_SERIAL_SPEED=115200
+ -DCMD_SERIAL_TIMEOUT=100000
+upload_port = /dev/ttyACM0
+
+
+# ===== RPi SPI =====
+[env:bootloader_spi]
+upload_protocol = rpi
+upload_flags =
+ -C
+ +avrdude-rpi.conf
+ -P
+ /dev/spidev0.0:/dev/gpiochip0
+extra_scripts =
+ pre:avrdude.py
+
+[_common_spi]
+extends =
+ _common
+build_flags =
+ ${_common.build_flags}
+ -DCMD_SPI
+ -DCDC_DISABLED
+upload_protocol = custom
+upload_flags =
+ -C
+ $PROJECT_PACKAGES_DIR/tool-avrdude/avrdude.conf
+ -C
+ +avrdude-rpi.conf
+ -P
+ /dev/spidev0.0:/dev/gpiochip0
+ -c
+ rpi
+ -p
+ $BOARD_MCU
+upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i
+
+[env:spi]
+extends =
+ _common_spi
+ _non_aum_pinout
+build_flags =
+ ${_common_spi.build_flags}
+ ${_non_aum_pinout.build_flags}
+
+[env:aum]
+extends =
+ _common_spi
+build_flags =
+ ${_common_spi.build_flags}
+ -DAUM
+ -DAUM_IS_USB_POWERED_PIN=A4
+ -DAUM_SET_USB_VBUS_PIN=11
+ -DAUM_SET_USB_CONNECTED_PIN=A5
+ -DHID_PS2_KBD_VBUS_PIN=8
+ -DHID_PS2_KBD_CLOCK_PIN=10
+ -DHID_PS2_KBD_DATA_PIN=5
+ -DHID_PS2_MOUSE_VBUS_PIN=6
+ -DHID_PS2_MOUSE_CLOCK_PIN=9
+ -DHID_PS2_MOUSE_DATA_PIN=13
diff --git a/hid/arduino/platformio-stm32.ini b/hid/arduino/platformio-stm32.ini
new file mode 100644
index 00000000..94fd44d4
--- /dev/null
+++ b/hid/arduino/platformio-stm32.ini
@@ -0,0 +1,52 @@
+# http://docs.platformio.org/page/projectconf.html
+[platformio]
+core_dir = ./.platformio/
+
+[env]
+framework = arduino
+platform = ststm32
+board = genericSTM32F103C8
+board_build.core = maple
+extra_scripts =
+ post:patch.py
+
+[_common]
+lib_deps =
+ git+https://github.com/ZulNs/STM32F1_RTC#v1.1.0
+ git+https://github.com/arpruss/USBComposite_stm32f1#3c58f97eb006ee9cd1fb4fd55ac4faeeaead0974
+ drivers-stm32
+build_flags =
+# ----- The default config with dynamic switching -----
+ -DHID_DYNAMIC
+ -DHID_WITH_USB
+ -DHID_SET_USB_KBD
+ -DHID_SET_USB_MOUSE_ABS
+
+[_serial]
+extends =
+ _common
+build_flags =
+ ${_common.build_flags}
+ -DCMD_SERIAL=Serial1
+ -DCMD_SERIAL_SPEED=115200
+ -DCMD_SERIAL_TIMEOUT=100000
+# ===== Serial =====
+[env:serial]
+extends =
+ _serial
+upload_flags = -c set CPUTAPID 0x2ba01477
+debug_tool= stlink
+debug_build_flags = -Og -ggdb3 -g3
+debug_server =
+ .platformio/packages/tool-openocd/bin/openocd
+ -s .platformio/packages/tool-openocd/scripts
+ -f interface/stlink.cfg
+ -c "transport select hla_swd"
+ -c "set CPUTAPID 0x2ba01477"
+ -f target/stm32f1x.cfg
+ -c "reset_config none"
+; build_type = debug
+[env:patch]
+; platformio-stm32f1-no-serial-usb.patch requires to running build again
+; fake target was added to avoid error during first build
+src_filter = -<src/>
diff --git a/hid/arduino/src/main.cpp b/hid/arduino/src/main.cpp
new file mode 100644
index 00000000..c1539106
--- /dev/null
+++ b/hid/arduino/src/main.cpp
@@ -0,0 +1,253 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#include <Arduino.h>
+
+#include "tools.h"
+#include "proto.h"
+#include "board.h"
+#include "outputs.h"
+#ifdef AUM
+# include "aum.h"
+#endif
+
+
+static DRIVERS::Connection *_conn;
+static DRIVERS::Board *_board;
+static Outputs _out;
+
+#ifdef HID_DYNAMIC
+# define RESET_TIMEOUT 500000
+static bool _reset_required = false;
+static unsigned long _reset_timestamp;
+#endif
+
+
+// -----------------------------------------------------------------------------
+#ifdef HID_DYNAMIC
+static void _resetRequest() {
+ _reset_required = true;
+ _reset_timestamp = micros();
+}
+#endif
+
+static void _cmdSetKeyboard(const uint8_t *data) { // 1 bytes
+# ifdef HID_DYNAMIC
+ _out.writeOutputs(PROTO::OUTPUTS1::KEYBOARD::MASK, data[0], false);
+ _resetRequest();
+# endif
+}
+
+static void _cmdSetMouse(const uint8_t *data) { // 1 bytes
+# ifdef HID_DYNAMIC
+ _out.writeOutputs(PROTO::OUTPUTS1::MOUSE::MASK, data[0], false);
+ _resetRequest();
+# endif
+}
+
+static void _cmdSetConnected(const uint8_t *data) { // 1 byte
+# ifdef AUM
+ aumSetUsbConnected(data[0]);
+# endif
+}
+
+static void _cmdClearHid(const uint8_t *_) { // 0 bytes
+ _out.kbd->clear();
+ _out.mouse->clear();
+}
+
+static void _cmdKeyEvent(const uint8_t *data) { // 2 bytes
+ _out.kbd->sendKey(data[0], data[1]);
+}
+
+static void _cmdMouseButtonEvent(const uint8_t *data) { // 2 bytes
+# define MOUSE_PAIR(_state, _button) \
+ _state & PROTO::CMD::MOUSE::_button::SELECT, \
+ _state & PROTO::CMD::MOUSE::_button::STATE
+ _out.mouse->sendButtons(
+ MOUSE_PAIR(data[0], LEFT),
+ MOUSE_PAIR(data[0], RIGHT),
+ MOUSE_PAIR(data[0], MIDDLE),
+ MOUSE_PAIR(data[1], EXTRA_UP),
+ MOUSE_PAIR(data[1], EXTRA_DOWN)
+ );
+# undef MOUSE_PAIR
+}
+
+static void _cmdMouseMoveEvent(const uint8_t *data) { // 4 bytes
+ // See /kvmd/apps/otg/hid/keyboard.py for details
+ _out.mouse->sendMove(
+ PROTO::merge8_int(data[0], data[1]),
+ PROTO::merge8_int(data[2], data[3])
+ );
+}
+
+static void _cmdMouseRelativeEvent(const uint8_t *data) { // 2 bytes
+ _out.mouse->sendRelative(data[0], data[1]);
+}
+
+static void _cmdMouseWheelEvent(const uint8_t *data) { // 2 bytes
+ // Y only, X is not supported
+ _out.mouse->sendWheel(data[1]);
+}
+
+static uint8_t _handleRequest(const uint8_t *data) { // 8 bytes
+ _board->updateStatus(DRIVERS::RX_DATA);
+ // FIXME: See kvmd/kvmd#80
+ // Should input buffer be cleared in this case?
+ if (data[0] == PROTO::MAGIC && PROTO::crc16(data, 6) == PROTO::merge8(data[6], data[7])) {
+# define HANDLE(_handler) { _handler(data + 2); return PROTO::PONG::OK; }
+ switch (data[1]) {
+ case PROTO::CMD::PING: return PROTO::PONG::OK;
+ case PROTO::CMD::SET_KEYBOARD: HANDLE(_cmdSetKeyboard);
+ case PROTO::CMD::SET_MOUSE: HANDLE(_cmdSetMouse);
+ case PROTO::CMD::SET_CONNECTED: HANDLE(_cmdSetConnected);
+ case PROTO::CMD::CLEAR_HID: HANDLE(_cmdClearHid);
+ case PROTO::CMD::KEYBOARD::KEY: HANDLE(_cmdKeyEvent);
+ case PROTO::CMD::MOUSE::BUTTON: HANDLE(_cmdMouseButtonEvent);
+ case PROTO::CMD::MOUSE::MOVE: HANDLE(_cmdMouseMoveEvent);
+ case PROTO::CMD::MOUSE::RELATIVE: HANDLE(_cmdMouseRelativeEvent);
+ case PROTO::CMD::MOUSE::WHEEL: HANDLE(_cmdMouseWheelEvent);
+ case PROTO::CMD::REPEAT: return 0;
+ default: return PROTO::RESP::INVALID_ERROR;
+ }
+# undef HANDLE
+ }
+ return PROTO::RESP::CRC_ERROR;
+}
+
+
+// -----------------------------------------------------------------------------
+static void _sendResponse(uint8_t code) {
+ static uint8_t prev_code = PROTO::RESP::NONE;
+ if (code == 0) {
+ code = prev_code; // Repeat the last code
+ } else {
+ prev_code = code;
+ }
+
+ uint8_t response[8] = {0};
+ response[0] = PROTO::MAGIC_RESP;
+ if (code & PROTO::PONG::OK) {
+ response[1] = PROTO::PONG::OK;
+# ifdef HID_DYNAMIC
+ if (_reset_required) {
+ response[1] |= PROTO::PONG::RESET_REQUIRED;
+ if (is_micros_timed_out(_reset_timestamp, RESET_TIMEOUT)) {
+ _board->reset();
+ }
+ }
+ response[2] = PROTO::OUTPUTS1::DYNAMIC;
+# endif
+ if (_out.kbd->getType() != DRIVERS::DUMMY) {
+ if(_out.kbd->isOffline()) {
+ response[1] |= PROTO::PONG::KEYBOARD_OFFLINE;
+ } else {
+ _board->updateStatus(DRIVERS::KEYBOARD_ONLINE);
+ }
+ DRIVERS::KeyboardLedsState leds = _out.kbd->getLeds();
+ response[1] |= (leds.caps ? PROTO::PONG::CAPS : 0);
+ response[1] |= (leds.num ? PROTO::PONG::NUM : 0);
+ response[1] |= (leds.scroll ? PROTO::PONG::SCROLL : 0);
+ switch (_out.kbd->getType()) {
+ case DRIVERS::USB_KEYBOARD:
+ response[2] |= PROTO::OUTPUTS1::KEYBOARD::USB;
+ break;
+ case DRIVERS::PS2_KEYBOARD:
+ response[2] |= PROTO::OUTPUTS1::KEYBOARD::PS2;
+ break;
+ }
+ }
+ if (_out.mouse->getType() != DRIVERS::DUMMY) {
+ if(_out.mouse->isOffline()) {
+ response[1] |= PROTO::PONG::MOUSE_OFFLINE;
+ } else {
+ _board->updateStatus(DRIVERS::MOUSE_ONLINE);
+ }
+ switch (_out.mouse->getType()) {
+ case DRIVERS::USB_MOUSE_ABSOLUTE_WIN98:
+ response[2] |= PROTO::OUTPUTS1::MOUSE::USB_WIN98;
+ break;
+ case DRIVERS::USB_MOUSE_ABSOLUTE:
+ response[2] |= PROTO::OUTPUTS1::MOUSE::USB_ABS;
+ break;
+ case DRIVERS::USB_MOUSE_RELATIVE:
+ response[2] |= PROTO::OUTPUTS1::MOUSE::USB_REL;
+ break;
+ }
+ } // TODO: ps2
+# ifdef AUM
+ response[3] |= PROTO::OUTPUTS2::CONNECTABLE;
+ if (aumIsUsbConnected()) {
+ response[3] |= PROTO::OUTPUTS2::CONNECTED;
+ }
+# endif
+# ifdef HID_WITH_USB
+ response[3] |= PROTO::OUTPUTS2::HAS_USB;
+# ifdef HID_WITH_USB_WIN98
+ response[3] |= PROTO::OUTPUTS2::HAS_USB_WIN98;
+# endif
+# endif
+# ifdef HID_WITH_PS2
+ response[3] |= PROTO::OUTPUTS2::HAS_PS2;
+# endif
+ } else {
+ response[1] = code;
+ }
+ PROTO::split16(PROTO::crc16(response, 6), &response[6], &response[7]);
+
+ _conn->write(response, 8);
+}
+
+static void _onTimeout() {
+ _sendResponse(PROTO::RESP::TIMEOUT_ERROR);
+}
+
+static void _onData(const uint8_t *data, size_t size) {
+ _sendResponse(_handleRequest(data));
+}
+
+void setup() {
+ _out.initOutputs();
+
+# ifdef AUM
+ aumInit();
+# endif
+
+ _conn = DRIVERS::Factory::makeConnection(DRIVERS::CONNECTION);
+ _conn->onTimeout(_onTimeout);
+ _conn->onData(_onData);
+ _conn->begin();
+
+ _board = DRIVERS::Factory::makeBoard(DRIVERS::BOARD);
+}
+
+void loop() {
+# ifdef AUM
+ aumProxyUsbVbus();
+# endif
+
+ _out.kbd->periodic();
+ _out.mouse->periodic();
+ _board->periodic();
+ _conn->periodic();
+}
diff --git a/hid/arduino/src/outputs.h b/hid/arduino/src/outputs.h
new file mode 100644
index 00000000..dd9008f3
--- /dev/null
+++ b/hid/arduino/src/outputs.h
@@ -0,0 +1,122 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#include "factory.h"
+#include "proto.h"
+
+
+class Outputs {
+ public:
+ void writeOutputs(uint8_t mask, uint8_t outputs, bool force) {
+ int old = 0;
+ if (!force) {
+ old = _readOutputs();
+ if (old < 0) {
+ old = 0;
+ }
+ }
+ uint8_t data[8] = {0};
+ data[0] = PROTO::MAGIC;
+ data[1] = (old & ~mask) | outputs;
+ PROTO::split16(PROTO::crc16(data, 6), &data[6], &data[7]);
+ _storage->updateBlock(data, 0, 8);
+ }
+
+ void initOutputs() {
+# ifdef HID_DYNAMIC
+ _storage = DRIVERS::Factory::makeStorage(DRIVERS::NON_VOLATILE_STORAGE);
+# else
+ _storage = DRIVERS::Factory::makeStorage(DRIVERS::DUMMY);
+# endif
+
+ int outputs = _readOutputs();
+ if (outputs < 0) {
+ outputs = 0;
+# if defined(HID_WITH_USB) && defined(HID_SET_USB_KBD)
+ outputs |= PROTO::OUTPUTS1::KEYBOARD::USB;
+# elif defined(HID_WITH_PS2) && defined(HID_SET_PS2_KBD)
+ outputs |= PROTO::OUTPUTS1::KEYBOARD::PS2;
+# endif
+# if defined(HID_WITH_USB) && defined(HID_SET_USB_MOUSE_ABS)
+ outputs |= PROTO::OUTPUTS1::MOUSE::USB_ABS;
+# elif defined(HID_WITH_USB) && defined(HID_SET_USB_MOUSE_REL)
+ outputs |= PROTO::OUTPUTS1::MOUSE::USB_REL;
+# elif defined(HID_WITH_PS2) && defined(HID_SET_PS2_MOUSE)
+ outputs |= PROTO::OUTPUTS1::MOUSE::PS2;
+# elif defined(HID_WITH_USB) && defined(HID_WITH_USB_WIN98) && defined(HID_SET_USB_MOUSE_WIN98)
+ outputs |= PROTO::OUTPUTS1::MOUSE::USB_WIN98;
+# endif
+ writeOutputs(0xFF, outputs, true);
+ }
+
+ uint8_t kbd_type = outputs & PROTO::OUTPUTS1::KEYBOARD::MASK;
+ switch (kbd_type) {
+ case PROTO::OUTPUTS1::KEYBOARD::USB:
+ kbd = DRIVERS::Factory::makeKeyboard(DRIVERS::USB_KEYBOARD);
+ break;
+ case PROTO::OUTPUTS1::KEYBOARD::PS2:
+ kbd = DRIVERS::Factory::makeKeyboard(DRIVERS::PS2_KEYBOARD);
+ break;
+ default:
+ kbd = DRIVERS::Factory::makeKeyboard(DRIVERS::DUMMY);
+ break;
+ }
+
+ uint8_t mouse_type = outputs & PROTO::OUTPUTS1::MOUSE::MASK;
+ switch (mouse_type) {
+ case PROTO::OUTPUTS1::MOUSE::USB_ABS:
+ mouse = DRIVERS::Factory::makeMouse(DRIVERS::USB_MOUSE_ABSOLUTE);
+ break;
+ case PROTO::OUTPUTS1::MOUSE::USB_WIN98:
+ mouse = DRIVERS::Factory::makeMouse(DRIVERS::USB_MOUSE_ABSOLUTE_WIN98);
+ break;
+ case PROTO::OUTPUTS1::MOUSE::USB_REL:
+ mouse = DRIVERS::Factory::makeMouse(DRIVERS::USB_MOUSE_RELATIVE);
+ break;
+ default:
+ mouse = DRIVERS::Factory::makeMouse(DRIVERS::DUMMY);
+ break;
+ }
+
+# ifdef ARDUINO_ARCH_AVR
+ USBDevice.attach();
+# endif
+
+ kbd->begin();
+ mouse->begin();
+ }
+
+ DRIVERS::Keyboard *kbd = nullptr;
+ DRIVERS::Mouse *mouse = nullptr;
+
+ private:
+ int _readOutputs(void) {
+ uint8_t data[8];
+ _storage->readBlock(data, 0, 8);
+ if (data[0] != PROTO::MAGIC || PROTO::crc16(data, 6) != PROTO::merge8(data[6], data[7])) {
+ return -1;
+ }
+ return data[1];
+ }
+
+ DRIVERS::Storage *_storage = nullptr;
+};
diff --git a/hid/arduino/src/proto.h b/hid/arduino/src/proto.h
new file mode 100644
index 00000000..5fbdd49c
--- /dev/null
+++ b/hid/arduino/src/proto.h
@@ -0,0 +1,142 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <https://www.gnu.org/licenses/>. #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+
+namespace PROTO {
+ const uint8_t MAGIC = 0x33;
+ const uint8_t MAGIC_RESP = 0x34;
+
+ namespace RESP { // Plain responses
+ // const uint8_t OK = 0x20; // Legacy
+ const uint8_t NONE = 0x24;
+ const uint8_t CRC_ERROR = 0x40;
+ const uint8_t INVALID_ERROR = 0x45;
+ const uint8_t TIMEOUT_ERROR = 0x48;
+ };
+
+ namespace PONG { // Complex response
+ const uint8_t OK = 0x80;
+ const uint8_t CAPS = 0b00000001;
+ const uint8_t SCROLL = 0b00000010;
+ const uint8_t NUM = 0b00000100;
+ const uint8_t KEYBOARD_OFFLINE = 0b00001000;
+ const uint8_t MOUSE_OFFLINE = 0b00010000;
+ const uint8_t RESET_REQUIRED = 0b01000000;
+ };
+
+ namespace OUTPUTS1 { // Complex request/responce flags
+ const uint8_t DYNAMIC = 0b10000000;
+ namespace KEYBOARD {
+ const uint8_t MASK = 0b00000111;
+ const uint8_t USB = 0b00000001;
+ const uint8_t PS2 = 0b00000011;
+ };
+ namespace MOUSE {
+ const uint8_t MASK = 0b00111000;
+ const uint8_t USB_ABS = 0b00001000;
+ const uint8_t USB_REL = 0b00010000;
+ const uint8_t PS2 = 0b00011000;
+ const uint8_t USB_WIN98 = 0b00100000;
+ };
+ };
+
+ namespace OUTPUTS2 { // Complex response
+ const uint8_t CONNECTABLE = 0b10000000;
+ const uint8_t CONNECTED = 0b01000000;
+ const uint8_t HAS_USB = 0b00000001;
+ const uint8_t HAS_PS2 = 0b00000010;
+ const uint8_t HAS_USB_WIN98 = 0b00000100;
+ }
+
+ namespace CMD {
+ const uint8_t PING = 0x01;
+ const uint8_t REPEAT = 0x02;
+ const uint8_t SET_KEYBOARD = 0x03;
+ const uint8_t SET_MOUSE = 0x04;
+ const uint8_t SET_CONNECTED = 0x05;
+ const uint8_t CLEAR_HID = 0x10;
+
+ namespace KEYBOARD {
+ const uint8_t KEY = 0x11;
+ };
+
+ namespace MOUSE {
+ const uint8_t MOVE = 0x12;
+ const uint8_t BUTTON = 0x13;
+ const uint8_t WHEEL = 0x14;
+ const uint8_t RELATIVE = 0x15;
+ namespace LEFT {
+ const uint8_t SELECT = 0b10000000;
+ const uint8_t STATE = 0b00001000;
+ };
+ namespace RIGHT {
+ const uint8_t SELECT = 0b01000000;
+ const uint8_t STATE = 0b00000100;
+ };
+ namespace MIDDLE {
+ const uint8_t SELECT = 0b00100000;
+ const uint8_t STATE = 0b00000010;
+ };
+ namespace EXTRA_UP {
+ const uint8_t SELECT = 0b10000000;
+ const uint8_t STATE = 0b00001000;
+ };
+ namespace EXTRA_DOWN {
+ const uint8_t SELECT = 0b01000000;
+ const uint8_t STATE = 0b00000100;
+ };
+ };
+ };
+
+ uint16_t crc16(const uint8_t *buffer, unsigned length) {
+ const uint16_t polinom = 0xA001;
+ uint16_t crc = 0xFFFF;
+
+ for (unsigned byte_count = 0; byte_count < length; ++byte_count) {
+ crc = crc ^ buffer[byte_count];
+ for (unsigned bit_count = 0; bit_count < 8; ++bit_count) {
+ if ((crc & 0x0001) == 0) {
+ crc = crc >> 1;
+ } else {
+ crc = crc >> 1;
+ crc = crc ^ polinom;
+ }
+ }
+ }
+ return crc;
+ }
+
+ inline int merge8_int(uint8_t from_a, uint8_t from_b) {
+ return (((int)from_a << 8) | (int)from_b);
+ }
+
+ inline uint16_t merge8(uint8_t from_a, uint8_t from_b) {
+ return (((uint16_t)from_a << 8) | (uint16_t)from_b);
+ }
+
+ inline void split16(uint16_t from, uint8_t *to_a, uint8_t *to_b) {
+ *to_a = (uint8_t)(from >> 8);
+ *to_b = (uint8_t)(from & 0xFF);
+ }
+};