diff --git a/src/kaleidoscope/device/virtual/DefaultHIDReportConsumer.cpp b/src/kaleidoscope/device/virtual/DefaultHIDReportConsumer.cpp
new file mode 100644
index 00000000..ed2dca6d
--- /dev/null
+++ b/src/kaleidoscope/device/virtual/DefaultHIDReportConsumer.cpp
@@ -0,0 +1,125 @@
+/* Kaleidoscope - Firmware for computer input devices
+ * Copyright (C) 2013-2019 Keyboard.io, Inc.
+ *
+ * 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, version 3.
+ *
+ * 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 .
+ */
+
+#ifdef KALEIDOSCOPE_VIRTUAL_BUILD
+
+#include "DefaultHIDReportConsumer.h"
+#include "MultiReport/Keyboard.h"
+#include "Logging.h"
+
+#include "virtual_io.h"
+
+#undef min
+#undef max
+
+#include
+
+namespace kaleidoscope {
+
+using namespace logging;
+
+// For each bit set in 'bitfield', output the corresponding string to 'stream'
+#define FOREACHBIT(bitfield, stream, str0, str1, str2, str3, str4, str5, str6, str7) \
+ if((bitfield) & 1<<0) stream << str0; \
+ if((bitfield) & 1<<1) stream << str1; \
+ if((bitfield) & 1<<2) stream << str2; \
+ if((bitfield) & 1<<3) stream << str3; \
+ if((bitfield) & 1<<4) stream << str4; \
+ if((bitfield) & 1<<5) stream << str5; \
+ if((bitfield) & 1<<6) stream << str6; \
+ if((bitfield) & 1<<7) stream << str7;
+
+void DefaultHIDReportConsumer::processHIDReport(
+ uint8_t id, const void *data, int len, int result) {
+ if (id != HID_REPORTID_NKRO_KEYBOARD) {
+ log_info("***Ignoring hid report with id = %d\n", id);
+ return;
+ }
+
+ const HID_KeyboardReport_Data_t &report_data
+ = *static_cast(data);
+
+ std::stringstream keypresses;
+ bool anything = false;
+
+ if (report_data.modifiers) anything = true;
+ else for (int i = 0; i < KEY_BYTES; i++) if (report_data.keys[i]) {
+ anything = true;
+ break;
+ }
+
+ if (!anything) {
+ keypresses << "none";
+ } else {
+ FOREACHBIT(report_data.modifiers, keypresses,
+ "lctrl ", "lshift ", "lalt ", "lgui ",
+ "rctrl ", "rshift ", "ralt ", "rgui ")
+ FOREACHBIT(report_data.keys[0], keypresses,
+ "NO_EVENT ", "ERROR_ROLLOVER ", "POST_FAIL ", "ERROR_UNDEFINED ",
+ "a ", "b ", "c ", "d ")
+ FOREACHBIT(report_data.keys[1], keypresses,
+ "e ", "f ", "g ", "h ", "i ", "j ", "k ", "l ")
+ FOREACHBIT(report_data.keys[2], keypresses,
+ "m ", "n ", "o ", "p ", "q ", "r ", "s ", "t ")
+ FOREACHBIT(report_data.keys[3], keypresses,
+ "u ", "v ", "w ", "x ", "y ", "z ", "1/! ", "2/@ ")
+ FOREACHBIT(report_data.keys[4], keypresses,
+ "3/# ", "4/$ ", "5/% ", "6/^ ", "7/& ", "8/* ", "9/( ", "0/) ")
+ FOREACHBIT(report_data.keys[5], keypresses,
+ "enter ", "esc ", "del/bksp ", "tab ",
+ "space ", "-/_ ", "=/+ ", "[/{ ")
+ FOREACHBIT(report_data.keys[6], keypresses,
+ "]/} ", "\\/| ", "#/~ ", ";/: ", "'/\" ", "`/~ ", ",/< ", "./> ")
+ FOREACHBIT(report_data.keys[7], keypresses,
+ "//? ", "capslock ", "F1 ", "F2 ", "F3 ", "F4 ", "F5 ", "F6 ")
+ FOREACHBIT(report_data.keys[8], keypresses,
+ "F7 ", "F8 ", "F9 ", "F10 ", "F11 ", "F12 ", "prtscr ", "scrolllock ")
+ FOREACHBIT(report_data.keys[9], keypresses,
+ "pause ", "ins ", "home ", "pgup ", "del ", "end ", "pgdn ", "r_arrow ")
+ FOREACHBIT(report_data.keys[10], keypresses,
+ "l_arrow ", "d_arrow ", "u_arrow ", "numlock ",
+ "num/ ", "num* ", "num- ", "num+ ")
+ FOREACHBIT(report_data.keys[11], keypresses,
+ "numenter ", "num1 ", "num2 ", "num3 ",
+ "num4 ", "num5 ", "num6 ", "num7 ")
+ FOREACHBIT(report_data.keys[12], keypresses,
+ "num8 ", "num9 ", "num0 ", "num. ", "\\/| ", "app ", "power ", "num= ")
+ FOREACHBIT(report_data.keys[13], keypresses,
+ "F13 ", "F14 ", "F15 ", "F16 ", "F17 ", "F18 ", "F19 ", "F20 ")
+ FOREACHBIT(report_data.keys[14], keypresses,
+ "F21 ", "F22 ", "F23 ", "F24 ", "exec ", "help ", "menu ", "sel ")
+ FOREACHBIT(report_data.keys[15], keypresses,
+ "stop ", "again ", "undo ", "cut ", "copy ", "paste ", "find ", "mute ")
+ FOREACHBIT(report_data.keys[16], keypresses,
+ "volup ", "voldn ", "capslock_l ", "numlock_l ",
+ "scrolllock_l ", "num, ", "num= ", "(other) ")
+
+ for (int i = 17; i < KEY_BYTES; i++) {
+ // A little imprecise, in two ways:
+ // (1) obviously, "(other)" refers to many distinct keys
+ // (2) this might undercount the number of "other" keys pressed
+ // Therefore, if any keys are frequently used, they should be handled above and not via "other"
+ if (report_data.keys[i]) keypresses << "(other) ";
+ }
+ }
+
+ log_info("Sent virtual HID report. Pressed keys: %s\n", keypresses.str().c_str());
+ logUSBEvent_keyboard("Keyboard HID report; pressed keys: " + keypresses.str());
+}
+
+} // namespace kaleidoscope
+
+#endif // ifdef KALEIDOSCOPE_VIRTUAL_BUILD
diff --git a/src/kaleidoscope/device/virtual/DefaultHIDReportConsumer.h b/src/kaleidoscope/device/virtual/DefaultHIDReportConsumer.h
new file mode 100644
index 00000000..77ce8552
--- /dev/null
+++ b/src/kaleidoscope/device/virtual/DefaultHIDReportConsumer.h
@@ -0,0 +1,34 @@
+/* Kaleidoscope - Firmware for computer input devices
+ * Copyright (C) 2013-2019 Keyboard.io, Inc.
+ *
+ * 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, version 3.
+ *
+ * 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 .
+ */
+
+#pragma once
+
+#ifdef KALEIDOSCOPE_VIRTUAL_BUILD
+
+#include
+
+namespace kaleidoscope {
+
+class DefaultHIDReportConsumer {
+ public:
+
+ static void processHIDReport(uint8_t id, const void *data,
+ int len, int result);
+};
+
+} // namespace kaleidoscope
+
+#endif // ifdef KALEIDOSCOPE_VIRTUAL_BUILD
diff --git a/src/kaleidoscope/device/virtual/HID.cpp b/src/kaleidoscope/device/virtual/HID.cpp
new file mode 100644
index 00000000..cf751f3f
--- /dev/null
+++ b/src/kaleidoscope/device/virtual/HID.cpp
@@ -0,0 +1,94 @@
+/*
+ Copyright (c) 2015, Arduino LLC
+ Original code (pre-library): Copyright (c) 2011, Peter Barrett
+
+ Permission to use, copy, modify, and/or distribute this software for
+ any purpose with or without fee is hereby granted, provided that the
+ above copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
+ BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
+ OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+ WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ SOFTWARE.
+ */
+
+// This is a modified version of the original HID.cpp from
+// library KeyboardioHID. It replaces all hardware related stuff
+// with stub implementations.
+
+// Include KeyboardioHID's HID.h header
+//
+#include "HID.h"
+
+#include "HIDReportObserver.h"
+
+#if defined(USBCON)
+
+HID_ &HID() {
+ static HID_ obj;
+ return obj;
+}
+
+int HID_::getInterface(uint8_t *interfaceCount) {
+ *interfaceCount += 1; // uses 1
+ return 0;
+}
+
+int HID_::getDescriptor(USBSetup &setup) {
+ return 1;
+}
+
+uint8_t HID_::getShortName(char *name) {
+ name[0] = 'v';
+ name[1] = 'i';
+ name[2] = 'r';
+ name[3] = 't';
+ name[4] = 'u';
+ name[5] = 'a';
+ name[6] = 'l';
+ return 7;
+}
+
+void HID_::AppendDescriptor(HIDSubDescriptor *node) {
+ if (!rootNode) {
+ rootNode = node;
+ } else {
+ HIDSubDescriptor *current = rootNode;
+
+ while (current->next) {
+ current = current->next;
+ }
+
+ current->next = node;
+ }
+
+ descriptorSize += node->length;
+}
+
+int HID_::SendReport(uint8_t id, const void *data, int len) {
+ HIDReportObserver::observeReport(id, data, len, 0);
+ return 1;
+}
+
+bool HID_::setup(USBSetup &setup) {
+ return true;
+}
+
+HID_::HID_(void) : PluggableUSBModule(1, 1, epType),
+ rootNode(NULL), descriptorSize(0),
+ protocol(HID_REPORT_PROTOCOL), idle(1) {
+ setReportData.reportId = 0;
+ setReportData.leds = 0;
+ epType[0] = EP_TYPE_INTERRUPT_IN;
+ //PluggableUSB().plug(this);
+}
+
+int HID_::begin(void) {
+ return 0;
+}
+
+#endif /* if defined(USBCON) */
diff --git a/src/kaleidoscope/device/virtual/Logging.cpp b/src/kaleidoscope/device/virtual/Logging.cpp
new file mode 100644
index 00000000..2acecbbc
--- /dev/null
+++ b/src/kaleidoscope/device/virtual/Logging.cpp
@@ -0,0 +1,33 @@
+/* Kaleidoscope - Firmware for computer input devices
+ * Copyright (C) 2013-2019 Keyboard.io, Inc.
+ *
+ * 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, version 3.
+ *
+ * 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 .
+ */
+
+#include "Logging.h"
+
+namespace kaleidoscope {
+namespace logging {
+
+static bool __verboseOutputEnabled = true;
+
+void toggleVerboseOutput(bool state) {
+ __verboseOutputEnabled = state;
+}
+
+bool verboseOutputEnabled() {
+ return __verboseOutputEnabled;
+}
+
+} // namespace logging
+} // namespace kaleidoscope
diff --git a/src/kaleidoscope/device/virtual/Logging.h b/src/kaleidoscope/device/virtual/Logging.h
new file mode 100644
index 00000000..94c592e3
--- /dev/null
+++ b/src/kaleidoscope/device/virtual/Logging.h
@@ -0,0 +1,96 @@
+/* Kaleidoscope - Firmware for computer input devices
+ * Copyright (C) 2013-2019 Keyboard.io, Inc.
+ *
+ * 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, version 3.
+ *
+ * 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 .
+ */
+
+#pragma once
+
+#ifdef KALEIDOSCOPE_VIRTUAL_BUILD
+
+#include "utility"
+#include "stdio.h"
+
+namespace kaleidoscope {
+namespace logging {
+
+extern void toggleVerboseOutput(bool state);
+extern bool verboseOutputEnabled();
+
+// Please note that we prefer stdio based logging against
+// stream based output, to stay compatible with possible future global
+// logging functions introduced by Kaleidoscope. Stream
+// based logging is not available on the target platform due to restricted
+// resources.
+
+// Below, we use perfect forwarding to pass printf style log messages
+// to different printf derivates. gcc warns when the the format string
+// is not a literal, which is not the case here due to our forwarding.
+// To silence the related warning, which is not desired in our application,
+// we disable the format-security diagnostic until the end of this file.
+
+// Disable diagnostic for literal format strings.
+//
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-security"
+
+#ifdef KALEIDOSCOPE_HARDWARE_VIRTUAL_NO_LOGGING
+
+template
+inline
+void log_debug(Args__&&... args) {}
+
+template
+inline
+void log_info(Args__&&... args) {}
+
+#else // #ifdef KALEIDOSCOPE_HARDWARE_VIRTUAL_NO_LOGGING
+
+template
+inline
+void log_debug(Args__&&... args) {
+ if (verboseOutputEnabled()) {
+ fprintf(stdout, std::forward(args)...);
+ }
+}
+
+template
+inline
+void log_info(Args__&&... args) {
+ if (verboseOutputEnabled()) {
+ fprintf(stdout, std::forward(args)...);
+ }
+}
+
+#endif // #ifdef KALEIDOSCOPE_HARDWARE_VIRTUAL_NO_LOGGING
+
+template
+inline
+void log_error(Args__&&... args) {
+ fprintf(stderr, std::forward(args)...);
+}
+
+template
+inline
+void log_critical(Args__&&... args) {
+ fprintf(stderr, std::forward(args)...);
+}
+
+} // namespace logging
+} // namespace kaleidoscope
+
+// Re-enable diagnostic for literal format strings.
+//
+#pragma GCC diagnostic pop
+
+#endif // ifdef KALEIDOSCOPE_VIRTUAL_BUILD
diff --git a/src/kaleidoscope/device/virtual/Virtual.cpp b/src/kaleidoscope/device/virtual/Virtual.cpp
new file mode 100644
index 00000000..e0ff4cf6
--- /dev/null
+++ b/src/kaleidoscope/device/virtual/Virtual.cpp
@@ -0,0 +1,396 @@
+/* -*- mode: c++ -*-
+ * Kaleidoscope-Hardware-Model01 -- Keyboard.io Model01 hardware support for Kaleidoscope
+ * Copyright (C) 2017-2019 Keyboard.io, Inc
+ *
+ * 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, version 3.
+ *
+ * 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 .
+ */
+
+#ifdef KALEIDOSCOPE_VIRTUAL_BUILD
+
+#include "kaleidoscope/device/virtual/Virtual.h"
+#include "kaleidoscope/device/virtual/DefaultHIDReportConsumer.h"
+#include "kaleidoscope/device/virtual/Logging.h"
+
+#include "kaleidoscope/keyswitch_state.h"
+#include "Kaleidoscope.h"
+
+#include "HIDReportObserver.h"
+#include "virtual_io.h"
+#include "EEPROM.h"
+
+#include
+#include
+#include
+
+// FIXME: This relates to virtual/cores/arduino/EEPROM.h.
+// EEPROM static data must be defined here as only
+// the device knows about the EEPROM size.
+//
+// It might be better to move the EEPROM.h header here as
+// well???
+
+uint8_t EERef::eeprom_[kaleidoscope::DeviceProps::StorageProps::length];
+
+EEPROMClass EEPROM;
+
+uint16_t EEPROMClass::length() {
+ return kaleidoscope::DeviceProps::StorageProps::length;
+}
+
+namespace kaleidoscope {
+namespace device {
+namespace virt {
+
+using namespace kaleidoscope::logging;
+
+//##############################################################################
+// VirtualKeyScanner
+//##############################################################################
+
+VirtualKeyScanner::VirtualKeyScanner()
+ : n_pressed_switches_{0},
+ n_previously_pressed_switches_{0},
+ read_matrix_enabled_{true} {
+}
+
+void VirtualKeyScanner::setup() {
+
+ HIDReportObserver::resetHook(&DefaultHIDReportConsumer::processHIDReport);
+
+ for (auto key_addr : KeyAddr::all()) {
+ keystates_[key_addr.toInt()] = KeyState::NotPressed;
+ keystates_prev_[key_addr.toInt()] = KeyState::NotPressed;
+ mask_[key_addr.toInt()] = false;
+ }
+}
+
+enum Mode {
+ M_TAP,
+ M_DOWN,
+ M_UP,
+};
+
+// FIXME: getRCfromPhysicalKey only works for the Model01 with default
+// layout. We might want to remove this as the virtual hardware
+// intents to simulate any type of keyboard.
+//
+// static KeyAddr getRCfromPhysicalKey(std::string keyname) {
+// if (keyname == "prog") return KeyAddr{0, 0};
+// else if (keyname == "1") return KeyAddr{0, 1};
+// else if (keyname == "2") return KeyAddr{0, 2};
+// else if (keyname == "3") return KeyAddr{0, 3};
+// else if (keyname == "4") return KeyAddr{0, 4};
+// else if (keyname == "5") return KeyAddr{0, 5};
+// else if (keyname == "led") return KeyAddr{0, 6};
+// else if (keyname == "any") return KeyAddr{0, 9};
+// else if (keyname == "6") return KeyAddr{0, 10};
+// else if (keyname == "7") return KeyAddr{0, 11};
+// else if (keyname == "8") return KeyAddr{0, 12};
+// else if (keyname == "9") return KeyAddr{0, 13};
+// else if (keyname == "0") return KeyAddr{0, 14};
+// else if (keyname == "num") return KeyAddr{0, 15};
+// else if (keyname == "`") return KeyAddr{1, 0};
+// else if (keyname == "q") return KeyAddr{1, 1};
+// else if (keyname == "w") return KeyAddr{1, 2};
+// else if (keyname == "e") return KeyAddr{1, 3};
+// else if (keyname == "r") return KeyAddr{1, 4};
+// else if (keyname == "t") return KeyAddr{1, 5};
+// else if (keyname == "tab") return KeyAddr{1, 6};
+// else if (keyname == "enter") return KeyAddr{1, 9};
+// else if (keyname == "y") return KeyAddr{1, 10};
+// else if (keyname == "u") return KeyAddr{1, 11};
+// else if (keyname == "i") return KeyAddr{1, 12};
+// else if (keyname == "o") return KeyAddr{1, 13};
+// else if (keyname == "p") return KeyAddr{1, 14};
+// else if (keyname == "=") return KeyAddr{1, 15};
+// else if (keyname == "pgup") return KeyAddr{2, 0};
+// else if (keyname == "a") return KeyAddr{2, 1};
+// else if (keyname == "s") return KeyAddr{2, 2};
+// else if (keyname == "d") return KeyAddr{2, 3};
+// else if (keyname == "f") return KeyAddr{2, 4};
+// else if (keyname == "g") return KeyAddr{2, 5};
+// else if (keyname == "h") return KeyAddr{2, 10};
+// else if (keyname == "j") return KeyAddr{2, 11};
+// else if (keyname == "k") return KeyAddr{2, 12};
+// else if (keyname == "l") return KeyAddr{2, 13};
+// else if (keyname == ";") return KeyAddr{2, 14};
+// else if (keyname == "'") return KeyAddr{2, 15};
+// else if (keyname == "pgdn") return KeyAddr{3, 0};
+// else if (keyname == "z") return KeyAddr{3, 1};
+// else if (keyname == "x") return KeyAddr{3, 2};
+// else if (keyname == "c") return KeyAddr{3, 3};
+// else if (keyname == "v") return KeyAddr{3, 4};
+// else if (keyname == "b") return KeyAddr{3, 5};
+// else if (keyname == "esc") return KeyAddr{2, 6}; // yes, row 2
+// else if (keyname == "fly") return KeyAddr{2, 9}; // yes, row 2
+// else if (keyname == "n") return KeyAddr{3, 10};
+// else if (keyname == "m") return KeyAddr{3, 11};
+// else if (keyname == ",") return KeyAddr{3, 12};
+// else if (keyname == ".") return KeyAddr{3, 13};
+// else if (keyname == "/") return KeyAddr{3, 14};
+// else if (keyname == "-") return KeyAddr{3, 15};
+// else if (keyname == "lctrl") return KeyAddr{0, 7};
+// else if (keyname == "bksp") return KeyAddr{1, 7};
+// else if (keyname == "cmd") return KeyAddr{2, 7};
+// else if (keyname == "lshift") return KeyAddr{3, 7};
+// else if (keyname == "rshift") return KeyAddr{3, 8};
+// else if (keyname == "alt") return KeyAddr{2, 8};
+// else if (keyname == "space") return KeyAddr{1, 8};
+// else if (keyname == "rctrl") return KeyAddr{0, 8};
+// else if (keyname == "lfn") return KeyAddr{3, 6};
+// else if (keyname == "rfn") return KeyAddr{3, 9};
+//
+// return KeyAddr{}; // Invalid key
+// }
+
+void VirtualKeyScanner::readMatrix() {
+
+ if (!read_matrix_enabled_) return;
+
+ std::stringstream sline;
+ sline << getLineOfInput(anythingHeld());
+ Mode mode = M_TAP;
+
+ while (true) {
+ std::string token;
+ std::getline(sline, token, ' ');
+
+ if (token == "") break; // end of line
+ else if (token == "#") break; // skip the rest of the line
+ else if ((token == "?" || token == "help") && isInteractive()) {
+ printHelp();
+ } else if (token == "Q") {
+ exit(0);
+ } else if (token == "T") {
+ mode = M_TAP;
+ } else if (token == "D") {
+ mode = M_DOWN;
+ } else if (token == "U") {
+ mode = M_UP;
+ } else if (token == "C") {
+ for (auto key_addr : KeyAddr::all()) {
+ keystates_[key_addr.toInt()] = KeyState::NotPressed;
+ }
+ } else {
+ KeyAddr key_addr;
+
+ if (token.front() == '(' && token.back() == ')') {
+ size_t commapos = token.find_first_of(',');
+
+ if (commapos == std::string::npos) {
+ log_error("Bad (r,c) pair: %s\n", token.c_str());
+ continue;
+ } else {
+ key_addr = KeyAddr(
+ (uint8_t)std::stoi(token.substr(1, commapos - 1)),
+ (uint8_t)std::stoi(token.substr(commapos + 1, token.length() - commapos - 1))
+ );
+
+ if (!key_addr.isValid()) {
+ log_error("Bad coordinates: %s\n", token.c_str());
+ continue;
+ }
+ }
+ } else {
+ // TODO: Is there a device independent
+ // way to determine KeyAddr from key names?
+// key_addr = getRCfromPhysicalKey(token);
+//
+// if (!key_addr.isValid()) {
+// log_error("Unrecognized command: %s\n", token.c_str());
+// continue;
+// }
+ }
+
+ keystates_[key_addr.toInt()] =
+ (mode == M_DOWN) ? KeyState::Pressed :
+ (mode == M_UP) ? KeyState::NotPressed :
+ KeyState::Tap;
+ }
+ }
+}
+void VirtualKeyScanner::actOnMatrixScan() {
+
+ n_pressed_switches_ = 0;
+ n_previously_pressed_switches_ = 0;
+
+ for (auto key_addr : KeyAddr::all()) {
+ uint8_t key_state = 0;
+
+ switch (keystates_prev_[key_addr.toInt()]) {
+ case KeyState::Pressed:
+ key_state |= WAS_PRESSED;
+ ++n_previously_pressed_switches_;
+ break;
+
+ case KeyState::Tap:
+ log_error("Error: assertion failed, keystates_prev_ should not be KeyState::Tap\n");
+ break;
+
+ case KeyState::NotPressed:
+ default:
+ /* do nothing */
+ break;
+ }
+
+ switch (keystates_[key_addr.toInt()]) {
+ case KeyState::Pressed:
+ case KeyState::Tap:
+ key_state |= IS_PRESSED;
+ ++n_pressed_switches_;
+ break;
+
+ case KeyState::NotPressed:
+ default:
+ /* do nothing */
+ break;
+ }
+
+ handleKeyswitchEvent(Key_NoKey, key_addr, key_state);
+ keystates_prev_[key_addr.toInt()] = keystates_[key_addr.toInt()];
+
+ if (keystates_[key_addr.toInt()] == KeyState::Tap) {
+ key_state = WAS_PRESSED & ~IS_PRESSED;
+ handleKeyswitchEvent(Key_NoKey, key_addr, key_state);
+ keystates_[key_addr.toInt()] = KeyState::NotPressed;
+ keystates_prev_[key_addr.toInt()] = KeyState::NotPressed;
+ }
+ }
+}
+
+uint8_t VirtualKeyScanner::pressedKeyswitchCount() const {
+ return n_pressed_switches_;
+}
+bool VirtualKeyScanner::isKeyswitchPressed(KeyAddr key_addr) const {
+ if (keystates_[key_addr.toInt()] == KeyState::NotPressed) {
+ return false;
+
+ }
+ return true;
+}
+
+uint8_t VirtualKeyScanner::previousPressedKeyswitchCount() const {
+ return n_previously_pressed_switches_;
+}
+bool VirtualKeyScanner::wasKeyswitchPressed(KeyAddr key_addr) const {
+ if (keystates_prev_[key_addr.toInt()] == KeyState::NotPressed) {
+ return false;
+
+ }
+
+ return true;
+}
+
+void VirtualKeyScanner::maskKey(KeyAddr key_addr) {
+ if (!key_addr.isValid()) {
+ log_error("Virtual::maskKey: key_addr invalid\n");
+ return;
+ }
+ mask_[key_addr.toInt()] = true;
+}
+
+void VirtualKeyScanner::unMaskKey(KeyAddr key_addr) {
+ if (!key_addr.isValid()) {
+ log_error("Virtual::unMaskKey: key_addr invalid\n");
+ return;
+ }
+ mask_[key_addr.toInt()] = false;
+}
+
+bool VirtualKeyScanner::isKeyMasked(KeyAddr key_addr) const {
+ if (!key_addr.isValid()) {
+ log_error("Virtual::isKeyMasked: key_addr invalid\n");
+ return false;
+ }
+ return mask_[key_addr.toInt()];
+}
+
+void VirtualKeyScanner::setKeystate(KeyAddr keyAddr, KeyState ks) {
+ keystates_[keyAddr.toInt()] = ks;
+}
+
+VirtualKeyScanner::KeyState VirtualKeyScanner::getKeystate(KeyAddr keyAddr) const {
+ return keystates_[keyAddr.toInt()];
+}
+
+bool VirtualKeyScanner::anythingHeld() {
+ for (auto key_addr : KeyAddr::all()) {
+ if (keystates_[key_addr.toInt()] == KeyState::Pressed) return true;
+ }
+
+ return false;
+}
+
+//##############################################################################
+// VirtualLEDDriver
+//##############################################################################
+
+void VirtualLEDDriver::setup() {
+ for (int i = 0; i < led_count; i++) {
+ led_states_[i] = CRGB(0, 0, 0);
+ }
+}
+
+void VirtualLEDDriver::syncLeds() {
+ // log format: red.green.blue where values are written in hex; followed by a space, followed by the next LED
+ std::stringstream ss;
+ ss << std::hex;
+
+ for (int i = 0; i < led_count; i++) {
+ const cRGB state = led_states_[i];
+ ss << (unsigned int) state.r << "." << (unsigned int) state.g << "." << (unsigned int) state.b << " ";
+ }
+
+ ss << std::endl;
+ logLEDStates(ss.str());
+}
+
+void VirtualLEDDriver::setCrgbAt(uint8_t i, cRGB color) {
+ if ((int)i >= (int)led_count) {
+ log_error("Virtual::setCrgbAt: Index %d out of bounds\n", i);
+ return;
+ }
+ led_states_[i] = color;
+}
+
+cRGB VirtualLEDDriver::getCrgbAt(uint8_t i) const {
+ if ((int)i >= (int)led_count) {
+ log_error("Virtual::getCrgbAt: Index %d out of bounds\n", i);
+ return CRGB(0, 0, 0);
+ }
+ return led_states_[i];
+}
+
+uint8_t VirtualLEDDriver::getLedIndex(uint8_t key_offset) const {
+ return kaleidoscope::DeviceProps::LEDDriverProps::getLedIndex(key_offset);
+}
+
+} // namespace virt
+} // namespace device
+
+
+namespace driver {
+namespace led {
+
+template<>
+Base::LEDs
+Base::LEDs_{};
+
+} // namespace led
+} // namespace driver
+
+} // namespace kaleidoscope
+
+
+
+#endif // ifdef KALEIDOSCOPE_VIRTUAL_BUILD
diff --git a/src/kaleidoscope/device/virtual/Virtual.h b/src/kaleidoscope/device/virtual/Virtual.h
new file mode 100644
index 00000000..4a880031
--- /dev/null
+++ b/src/kaleidoscope/device/virtual/Virtual.h
@@ -0,0 +1,160 @@
+/* -*- mode: c++ -*-
+ * Kaleidoscope-Hardware-Model01 -- Keyboard.io Model01 hardware support for Kaleidoscope
+ * Copyright (C) 2017-2019 Keyboard.io, Inc
+ *
+ * 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, version 3.
+ *
+ * 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 .
+ */
+
+#pragma once
+
+#ifdef KALEIDOSCOPE_VIRTUAL_BUILD
+
+#include KALEIDOSCOPE_HARDWARE_H
+
+#include "kaleidoscope/driver/bootloader/None.h"
+#include "kaleidoscope/driver/led/Base.h"
+
+namespace kaleidoscope {
+namespace device {
+namespace virt {
+
+class VirtualKeyScanner
+ : public kaleidoscope::driver::keyscanner::Base {
+ private:
+
+ typedef VirtualKeyScanner ThisType;
+ typedef kaleidoscope::driver::keyscanner::Base ParentType;
+
+ public:
+
+ typedef typename ParentType::KeyAddr KeyAddr;
+
+ enum class KeyState {
+ Pressed,
+ NotPressed,
+ Tap
+ };
+
+ static constexpr uint8_t matrix_rows = kaleidoscope::DeviceProps::KeyScannerProps::matrix_rows;
+ static constexpr uint8_t matrix_columns = kaleidoscope::DeviceProps::KeyScannerProps::matrix_columns;
+
+ VirtualKeyScanner();
+
+ void setup();
+ void readMatrix();
+ void scanMatrix() {
+ this->readMatrix();
+ this->actOnMatrixScan();
+ }
+ void actOnMatrixScan();
+
+ uint8_t pressedKeyswitchCount() const;
+ bool isKeyswitchPressed(KeyAddr key_addr) const;
+
+ uint8_t previousPressedKeyswitchCount() const;
+ bool wasKeyswitchPressed(KeyAddr key_addr) const;
+
+ void maskKey(KeyAddr key_addr);
+ void unMaskKey(KeyAddr key_addr);
+ bool isKeyMasked(KeyAddr key_addr) const;
+
+ void setEnableReadMatrix(bool state) {
+ read_matrix_enabled_ = state;
+ }
+
+ void setKeystate(KeyAddr keyAddr, KeyState ks);
+ KeyState getKeystate(KeyAddr keyAddr) const;
+
+ private:
+
+ bool anythingHeld();
+
+ private:
+
+ uint8_t n_pressed_switches_,
+ n_previously_pressed_switches_;
+
+ bool read_matrix_enabled_;
+
+ KeyState keystates_[matrix_rows * matrix_columns];
+ KeyState keystates_prev_[matrix_rows * matrix_columns];
+
+ bool mask_[matrix_rows * matrix_columns];
+};
+
+class VirtualLEDDriver
+ : public driver::led::Base {
+ public:
+
+ typedef driver::led::Base
+ ParentType;
+
+ using typename ParentType::LEDs;
+
+ static constexpr uint8_t led_count = kaleidoscope::DeviceProps::LEDDriverProps::led_count;
+
+ void setup();
+ void syncLeds();
+ void setCrgbAt(uint8_t i, cRGB color);
+ cRGB getCrgbAt(uint8_t i) const;
+ uint8_t getLedIndex(uint8_t key_offset) const;
+
+ private:
+
+ cRGB led_states_[led_count];
+};
+
+// This overrides only the drivers and keeps the driver props of
+// the physical keyboard.
+//
+struct VirtualProps : public kaleidoscope::DeviceProps {
+ typedef typename kaleidoscope::DeviceProps::KeyScannerProps
+ KeyScannerProps;
+ typedef VirtualKeyScanner
+ KeyScanner;
+ typedef KeyScannerProps::KeyAddr
+ KeyAddr;
+
+ typedef typename kaleidoscope::DeviceProps::LEDDriverProps
+ LEDDriverProps;
+ typedef VirtualLEDDriver
+ LEDDriver;
+
+ typedef kaleidoscope::driver::mcu::None MCU;
+
+ typedef kaleidoscope::driver::bootloader::None
+ BootLoader;
+
+ typedef typename kaleidoscope::DeviceProps::StorageProps
+ StorageProps;
+ typedef typename kaleidoscope::DeviceProps::Storage
+ Storage;
+};
+
+class VirtualDevice
+ : public kaleidoscope::device::Base {
+ public:
+
+ auto serialPort() -> decltype(Serial) & {
+ return Serial;
+ }
+};
+
+} // namespace virt
+} // namespace device
+
+typedef device::virt::VirtualDevice Device;
+
+} // namespace kaleidoscope
+
+#endif // ifdef KALEIDOSCOPE_VIRTUAL_BUILD