Compare commits

..

7 Commits

Author SHA1 Message Date
Michael Richters ab7e4187d1
Update Redial testcase to use default keymap
4 years ago
Michael Richters f50af857aa
Update Qukeys test suite to use the keymap definition stuff
4 years ago
Michael Richters 1c559260fd
Add basic OneShot testcase
4 years ago
Michael Richters 7ef7b4ba57
Add common keymap files to simplify test sketches
4 years ago
Michael Richters 9ee4c931f3
Use new testing infrastructure in Qukeys test cases
4 years ago
Michael Richters 278bea785b
Use new testing infrastructure in Redial test cases
4 years ago
Michael Richters c4139e48f7
Add Millis and Cycles types to enable polymorphic Run, PressKey, ReleaseKey, etc.
4 years ago

@ -1,7 +0,0 @@
style=google
unpad-paren
pad-header
pad-oper
indent-classes
indent=spaces=2
max-continuation-indent=80

@ -1,47 +0,0 @@
# -*- mode: yaml -*-
---
BasedOnStyle: Google
---
Language: Cpp
AlignConsecutiveAssignments: Consecutive
## clang-format-15
# AlignConsecutiveAssignments:
# Enabled: true
# AlignCompound: true
# PadOperators: true
AlignConsecutiveDeclarations: None
AlignConsecutiveMacros: AcrossEmptyLines
AlignEscapedNewlines: Right
AllowShortBlocksOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortFunctionsOnASingleLine: Inline
AllowShortLoopsOnASingleLine: true
AttributeMacros:
- __attribute__((weak))
- __attribute__((always_inline))
- __attribute__((noinline))
- __attribute__((packed))
- __attribute__((optimize(3)))
- __attribute__((unused))
BinPackArguments: false
BinPackParameters: false
# BraceWrapping:
# SplitEmptyFunction: false
# SplitEmptyRecord: true
# SplitEmptyNamespace: true
# BreakBeforeBraces: Custom
ColumnLimit: 0
ConstructorInitializerIndentWidth: 2
ContinuationIndentWidth: 2
DerivePointerAlignment: false
FixNamespaceComments: true
IndentCaseLabels: false
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
# PackConstructorInitializers: CurrentLine
PointerAlignment: Right
# ReferenceAlignment: Right
ReflowComments: false
SortIncludes: false
SpaceAfterTemplateKeyword: false

@ -1,9 +0,0 @@
set noparent
extensions=cpp,h,ino
filter=-build/include
filter=-legal/copyright
filter=-readability/namespace
filter=-runtime/references
filter=-whitespace

@ -1,9 +0,0 @@
set noparent
extensions=cpp,h,ino
filter=-build/include
filter=-legal/copyright
filter=-readability/namespace
filter=-runtime/references
filter=-whitespace/line_length

@ -1,26 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
**Expected behavior**
A clear and concise description of what you expected to happen.
**Environment (please complete the following information):**
- OS: [e.g. macOS]
- Version [e.g. 11.1]
- Device [e.g. Keyboardio Model 01]
**Additional context**
Add any other context about the problem here.

@ -1,18 +0,0 @@
---
name: Design change
about: What do you want to change?
title: ''
labels: design-change
assignees: ''
---
Larger than a feature request, this type of issue is for describing a proposed change to how Kaleidoscope works, in advance of a draft PR.
What do you want to change?
How will it make Kaleidoscope better?
What trouble might users have in adapting to the new functionality?
What trouble might developers have in adapting to the new functionality?

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

@ -1,66 +0,0 @@
name: Build
on: [push, pull_request]
env:
LC_ALL: C
ARDUINO_DIRECTORIES_USER: ${{ github.workspace }}/.arduino/user
CLANG_FORMAT_CMD: clang-format-12
jobs:
smoke-sketches:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Cache arduino dep downloads
uses: actions/cache@v2
with:
path: ${{ github.workspace}}/.arduino/downloads
key: ${{ runner.os }}-arduino-downloads
- run: make setup
- run: KALEIDOSCOPE_TEMP_PATH=${{ github.workspace}}/.kaleidoscope-temp make -j $(nproc) smoke-sketches
run-google-tests:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Cache arduino dep downloads
uses: actions/cache@v2
with:
path: ${{ github.workspace}}/.arduino/downloads
key: ${{ runner.os }}-arduino-downloads
- run: sudo apt update
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
- run: make setup
- run: KALEIDOSCOPE_CCACHE=1 make -j $(nproc) --output-sync=recurse simulator-tests
check-code-style:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: KALEIDOSCOPE_CODE_FORMATTER=clang-format-12 make check-code-style
check-shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: make shellcheck
check-cpplint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: make cpplint
find-filename-conflicts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: make find-filename-conflicts
publish-arduino-package-on-every-commit:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v2
- uses: webfactory/ssh-agent@v0.4.1
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY_DEPLOY }}
- run: git config --global user.email "nobody@keyboard.io"
- run: git config --global user.name "Automated release publisher"
- run: make setup
- run: make build-arduino-nightly-package

4
.gitignore vendored

@ -1,16 +1,12 @@
*~ *~
.arduino
/.docker_xfer /.docker_xfer
.#* .#*
/output/ /output/
/examples/*/output/ /examples/*/output/
/out/ /out/
/docs/plugins
/docs/generated /docs/generated
/docs/doxyoutput /docs/doxyoutput
/docs/api /docs/api
/_build/ /_build/
/results/ /results/
generated-testcase.cpp generated-testcase.cpp
.arduino
/bin/arduino-cli

@ -1,14 +0,0 @@
examples
src/Kaleidoscope.h
src/Kaleidoscope-LEDControl.h
src/kaleidoscope/HIDTables.h
src/kaleidoscope/driver/bootloader/gd32/Base.h
src/kaleidoscope/driver/led/Base.h
src/kaleidoscope/driver/led/WS2812.h
src/kaleidoscope/driver/led/ws2812/config.h
src/kaleidoscope/driver/mcu/GD32.h
src/kaleidoscope/driver/storage/GD32Flash.h
plugins/Kaleidoscope-FirmwareDump/**
plugins/Kaleidoscope-HostOS/src/kaleidoscope/plugin/HostOS.h
plugins/Kaleidoscope-Hardware-EZ-ErgoDox/src/kaleidoscope/device/ez/ErgoDox/i2cmaster.h
testing/googletest

@ -0,0 +1,3 @@
# -*- mode: sh -*-
DEFAULT_SKETCH=Kaleidoscope

@ -0,0 +1,57 @@
dist: focal
arch: amd64
language: c
os:
- linux
addons:
apt:
packages:
- shellcheck
- cmake
- astyle
env:
global:
- LC_ALL: C
- CCACHE_WRAPPER_PATH: /tmp/kaleidoscope-ccache
- ARDUINO_DOWNLOAD_URL: https://downloads.arduino.cc/arduino-1.8.13-linux64.tar.xz
git:
quiet: true
depth: false
before_install:
- sh .travis/setup-ramdisk.sh
- ccache --set-config=compiler_check=content -M 1G -F 0
install:
- cd $TRAVIS_BUILD_DIR
- git clone --depth 1 --recurse-submodules=build-tools --recurse-submodules=toolchain --recurse-submodules=avr/libraries/ --jobs 16 --shallow-submodules git://github.com/keyboardio/Kaleidoscope-Bundle-Keyboardio ../hardware/keyboardio
## We delete the Bundle's version of Kaleidoscope, and symlink ourselves in.
## This makes sure we're using the current version of the library.
- rm -rf ../hardware/keyboardio/avr/libraries/Kaleidoscope
- ln -s $(pwd) ../hardware/keyboardio/avr/libraries/Kaleidoscope
- export KALEIDOSCOPE_TEMP_PATH=$TRAVIS_BUILD_DIR/.kaleidoscope-build-cache
- export BOARD_HARDWARE_PATH=$TRAVIS_BUILD_DIR/../hardware
- export ARDUINO_PATH=$TRAVIS_BUILD_DIR/arduino-1.8.13
- make adjust-git-timestamps
- wget --quiet $ARDUINO_DOWNLOAD_URL -O - | tar xJf -
- make prepare-ccache
jobs:
include:
- env: TEST_CMD="make -j 2 smoke-sketches"
- env: TEST_CMD="CCACHE_NOT_SUPPORTED=1 make simulator-tests"
- env: TEST_CMD="make cpplint"
- env: TEST_CMD="make find-filename-conflicts"
- env: TEST_CMD="make shellcheck"
- env: TEST_CMD="make check-astyle"
script:
- unset CC
- eval $TEST_CMD
notifications:
email:
on_success: change
on_failure: change
cache:
apt: true
ccache: true
directories:
- .download-cache
- .kaleidoscope-build-cache

@ -0,0 +1,16 @@
#!/bin/bash
export BLDDIR=/home/travis/build
df -h
du -sh $HOME
du -sh $BLDDIR
sudo mv $BLDDIR $BLDDIR.ori
sudo mkdir -p $BLDDIR
sudo mount -t tmpfs -o size=8192m tmps $BLDDIR
time sudo cp -R $BLDDIR.ori/. $BLDDIR
sudo chown -R travis:travis $BLDDIR
df -h
du -sh "$HOME"
du -sh "$BLDDIR"

@ -1,170 +1,142 @@
# Reset a bunch of historical GNU make implicit rules that we never # This stub makefile for a Kaleidoscope plugin pulls in
# use, but which have a disastrous impact on performance # all targets from the Kaleidoscope-Plugin library
#
# --no-builtin-rules in MAKEFLAGS apparently came in with GNU Make 4,
# which is newer than what Apple ships
MAKEFLAGS += --no-builtin-rules
# These lines reset the implicit rules we really care about UNAME_S := $(shell uname -s)
%:: %,v
%:: RCS/%,v ifeq ($(UNAME_S),Darwin)
SKETCHBOOK_DIR ?= $(HOME)/Documents/Arduino
PACKAGE_DIR ?= $(HOME)/Library/Arduino15
else
SKETCHBOOK_DIR ?= $(HOME)/Arduino
PACKAGE_DIR ?= $(HOME)/.arduino15
endif
%:: RCS/%
%:: s.% ARDUINO_INSTALLED_ENV=$(shell ls -dt $(PACKAGE_DIR)/packages/keyboardio/hardware/avr 2>/dev/null |head -n 1)
MANUALLY_INSTALLED_ENV=$(shell ls -dt $(SKETCHBOOK_DIR)/hardware/keyboardio/avr 2>/dev/null |head -n 1)
%:: SCCS/s.%
.SUFFIXES:
# GNU Make earlier than 4.0 don't have the output-sync option, but we need it ifneq ("$(wildcard $(ARDUINO_INSTALLED_ENV)/boards.txt)","")
# to make parallel simulator test output readable. (otherwise it gets interleaved)
ifeq ($(shell test $(firstword $(subst ., ,$(MAKE_VERSION))) -ge 4; echo $$?),0) ifneq ("$(wildcard $(MANUALLY_INSTALLED_ENV)/boards.txt)","")
MAKEFLAGS+=--output-sync=target
else $(info ***************************************************************************)
_using_old_make=1 $(info It appears that you have installed two copies of Kaleidoscope. One copy was)
$(info installed using Arduino's "Board Manager", while the other was installed by)
$(info hand, probably using "git".)
$(info )
$(info This will likely cause some trouble as you try to build keyboard firmware)
$(info using Kaleidoscope. You may want to remove either: )
$(info )
$(info $(PACKAGE_DIR)/packages/keyboardio/ which was installed using Arduino)
$(info )
$(info or)
$(info )
$(info $(SKETCHBOOK_DIR)/hardware/keyboardio/ which was installed by hand.)
$(info )
$(info ***************************************************************************)
$(info )
endif endif
include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/etc/makefiles/arduino-cli.mk BOARD_HARDWARE_PATH = $(ARDUINO_INSTALLED_ENV)
KALEIDOSCOPE_PLUGIN_MAKEFILE_DIR ?= build-tools/makefiles/
KALEIDOSCOPE_BUILDER_DIR ?= $(ARDUINO_INSTALLED_ENV)/libraries/Kaleidoscope/bin/
# Set up an argument for passing to the simulator tests in docker
# but if the var isn't set, don't even pass the definition
# since this messes with downstream makefiles
ifneq ($(TEST_PATH),)
TEST_PATH_ARG="TEST_PATH='$(TEST_PATH)'"
endif endif
.DEFAULT_GOAL := smoke-sketches
BOARD_HARDWARE_PATH ?= $(SKETCHBOOK_DIR)/hardware
.PHONY: setup KALEIDOSCOPE_PLUGIN_MAKEFILE_DIR ?= keyboardio/build-tools/makefiles/
setup: $(ARDUINO_CLI_PATH) $(ARDUINO_DIRECTORIES_DATA)/arduino-cli.yaml install-arduino-core-avr install-arduino-core-kaleidoscope checkout-platform prepare-virtual
@: # If Kaleidoscope's Arduino libraries cannot be found, e.g. because
# they reside outside of SKETCHBOOK_DIR, we fall back to assuming that
.PHONY: checkout-platform # the hardware directory can be determined in relation to the position of
checkout-platform: $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/avr/boards.txt # this Makefile.
@: ifeq ("$(wildcard $(BOARD_HARDWARE_PATH)/keyboardio/build-tools/makefiles/rules.mk)","")
# Determine the path of this Makefile
$(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/avr/boards.txt: MKFILE_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
git clone -c core.symlinks=true \ BOARD_HARDWARE_PATH = $(MKFILE_DIR)/../../../..
--recurse-submodules \
--shallow-submodules \
--recurse-submodules=':(exclude)avr/bootloaders' \
--recurse-submodules=':(exclude)avr/libraries/Kaleidoscope' \
https://github.com/keyboardio/Kaleidoscope-Bundle-Keyboardio \
$(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio
git clone -c core.symlinks=true \
--recurse-submodules \
--shallow-submodules \
--recurse-submodules=':(exclude)libraries/Kaleidoscope' \
https://github.com/keyboardio/ArduinoCore-GD32-Keyboardio $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/gd32
.PHONY: prepare-virtual
prepare-virtual: $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/virtual/boards.txt
@:
$(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/virtual/boards.txt:
$(MAKE) -C $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio prepare-virtual
.PHONY: update
update:
cd $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio; git pull; \
git submodule update --init --recursive
cd $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/gd32; git pull; \
git submodule update --init --recursive
.PHONY: simulator-tests
simulator-tests:
ifneq ($(_using_old_make),)
$(info You're using an older version of GNU Make that doesn't offer the --output-sync option. If you're running the test suite in parallel, output may be garbled. You might consider using GNU Make 4.0 or later instead)
endif endif
ifeq ("$(wildcard $(BOARD_HARDWARE_PATH)/keyboardio/build-tools/makefiles/rules.mk)","")
$(info ***************************************************************************)
$(info Unable to autodetect a proper BOARD_HARDWARE_PATH. Please define it manually.)
$(info ***************************************************************************)
$(info )
endif
include $(BOARD_HARDWARE_PATH)/$(KALEIDOSCOPE_PLUGIN_MAKEFILE_DIR)/rules.mk
prepare-virtual:
$(MAKE) -C $(BOARD_HARDWARE_PATH)/keyboardio prepare-virtual
simulator-tests: prepare-virtual
$(MAKE) -C tests all $(MAKE) -C tests all
.PHONY: docker-simulator-tests
docker-simulator-tests: docker-simulator-tests:
ARDUINO_DIRECTORIES_USER="$(ARDUINO_DIRECTORIES_USER)" ./bin/run-docker "make simulator-tests $(TEST_PATH_ARG)" BOARD_HARDWARE_PATH="$(BOARD_HARDWARE_PATH)" ./bin/run-docker "make simulator-tests TEST_PATH=\"${TEST_PATH}\""
.PHONY: docker-clean
docker-clean:
_NO_SYNC_KALEIDOSCOPE=1 ARDUINO_DIRECTORIES_USER="$(ARDUINO_DIRECTORIES_USER)" ./bin/run-docker "rm -rf -- testing/googletest/build/* _build/* /kaleidoscope-persist/temp/*"
.PHONY: docker-bash
docker-bash: docker-bash:
_NO_SYNC_KALEIDOSCOPE=1 DOCKER_LIVE_KALEIDOSCOPE_DIR=1 ARDUINO_DIRECTORIES_USER="$(ARDUINO_DIRECTORIES_USER)" ./bin/run-docker "bash" BOARD_HARDWARE_PATH="$(BOARD_HARDWARE_PATH)" ./bin/run-docker "bash"
run-tests: prepare-virtual build-gtest-gmock
$(MAKE) -c tests
@: # blah
build-gtest-gmock: build-gtest-gmock:
(cd testing/googletest && cmake -H. -Bbuild -DCMAKE_C_COMPILER=$(call _arduino_prop,compiler.path)$(call _arduino_prop,compiler.c.cmd) -DCMAKE_CXX_COMPILER=$(call _arduino_prop,compiler.path)$(call _arduino_prop,compiler.cpp.cmd) .) (cd testing/googletest && cmake .)
$(MAKE) -C testing/googletest $(MAKE) -C testing/googletest
.PHONY: find-filename-conflicts adjust-git-timestamps:
bin/set-timestamps-from-git
find-filename-conflicts: find-filename-conflicts:
bin/find-filename-conflicts.py src plugins/* @if [ -d "bin" ]; then \
bin/find-filename-conflicts; \
.PHONY: format fi
format:
bin/format-code.py \ #.PHONY: astyle test cpplint cpplint-noisy shellcheck smoke-examples find-filename-conflicts:
--exclude-dir 'testing/googletest' \
--exclude-file 'generated-testcase.cpp' \ astyle:
src plugins examples testing PATH="$(PLUGIN_TEST_BIN_DIR):$(PATH)" $(PLUGIN_TEST_SUPPORT_DIR)/quality/run-astyle
.PHONY: check-code-style check-astyle: astyle
check-code-style: PATH="$(PLUGIN_TEST_BIN_DIR):$(PATH)" $(PLUGIN_TEST_SUPPORT_DIR)/quality/astyle-check
bin/format-code.py \
--exclude-dir 'testing/googletest' \
--exclude-file 'generated-testcase.cpp' \
--check \
--verbose \
src plugins examples testing
.PHONY: check-includes
check-includes:
bin/fix-header-includes
.PHONY: check-all-includes
check-all-includes:
bin/iwyu.py -v src plugins
bin/iwyu.py -v \
-I=$(KALEIDOSCOPE_DIR) \
-I=$(KALEIDOSCOPE_DIR)/testing/googletest/googlemock/include \
-I=$(KALEIDOSCOPE_DIR)/testing/googletest/googletest/include \
testing
bin/format-code.py -f -v --check \
--exclude-dir 'testing/googletest' \
src plugins testing
.PHONY: cpplint-noisy
cpplint-noisy: cpplint-noisy:
-bin/cpplint.py --config=.cpplint-noisy --recursive src plugins examples -$(PLUGIN_TEST_SUPPORT_DIR)/quality/cpplint.py --filter=-legal/copyright,-build/include,-readability/namespace,-whitespace/line_length,-runtime/references --recursive --extensions=cpp,h,ino --exclude=$(BOARD_HARDWARE_PATH) src examples
.PHONY: cpplint
cpplint: cpplint:
bin/cpplint.py --config=.cpplint --quiet --recursive src plugins examples $(PLUGIN_TEST_SUPPORT_DIR)/quality/cpplint.py --quiet --filter=-whitespace,-legal/copyright,-build/include,-readability/namespace,-runtime/references --recursive --extensions=cpp,h,ino src examples
SHELL_FILES = $(shell if [ -d bin ]; then egrep -n -r -l "(env (ba)?sh)|(/bin/(ba)?sh)" bin; fi)
.PHONY: shellcheck
shellcheck: shellcheck:
bin/check-shell-scripts.sh @if [ -d "bin" ]; then \
shellcheck ${SHELL_FILES}; \
fi
check-docs:
doxygen $(PLUGIN_TEST_SUPPORT_DIR)/quality/etc/check-docs.conf 2> /dev/null >/dev/null
python $(PLUGIN_TEST_SUPPORT_DIR)/quality/doxy-coverage.py /tmp/undocced/xml
SMOKE_SKETCHES := $(sort $(shell if [ -d ./examples ]; then find ./examples -type f -name \*ino | xargs -n 1 dirname; fi))
SMOKE_SKETCHES=$(shell if [ -d ./examples ]; then find ./examples -type f -name \*ino | xargs -n 1 dirname; fi)
smoke-sketches: $(SMOKE_SKETCHES) smoke-sketches: $(SMOKE_SKETCHES)
@echo "Smoke-tested all the sketches" @echo "Smoke-tested all the sketches"
.PHONY: force .PHONY: force
$(SMOKE_SKETCHES): force $(SMOKE_SKETCHES): force
$(MAKE) -C $@ -f $(KALEIDOSCOPE_ETC_DIR)/makefiles/sketch.mk compile @BOARD_HARDWARE_PATH="$(BOARD_HARDWARE_PATH)" $(KALEIDOSCOPE_BUILDER_DIR)/kaleidoscope-builder $@ compile
.PHONY: clean
clean:
$(MAKE) -C tests clean
rm -rf -- "testing/googletest/build/*"
rm -rf -- "_build/*"
build-arduino-nightly-package:
perl bin/build-arduino-package \
--kaleidoscope-tag=master \
--version `date +%Y.%-m.%-d%H%M%S` \
--index-filename-slug=kaleidoscope_master \
--only-one-platform-revision \
--push \
--output-repo=ssh://git@github.com/keyboardio/arduino-kaleidoscope-master

@ -1,67 +1,72 @@
# Kaleidoscope # Kaleidoscope
Flexible firmware for Arduino-powered keyboards. Flexible firmware for Arduino-powered keyboards.
This package contains the "core" of Kaleidoscope and a number of [example firmware "Sketches"](https://github.com/keyboardio/Kaleidoscope/tree/master/examples). This package contains the "core" of Kaleidoscope and a number of [example firmware "Sketches"](https://github.com/keyboardio/Kaleidoscope/tree/master/examples).
If you're just getting started with the Keyboardio Model 01, the [introductory docs are here](https://github.com/keyboardio/Kaleidoscope/wiki/Keyboardio-Model-01-Introduction) and the source for the basic firmware package is here: https://github.com/keyboardio/Model01-Firmware. It's probably a good idea to start there, learn how to modify your keymap and maybe turn some modules on or off, and then come back to the full repository when you have more complex changes in mind. (The firmware for all other devices is inside examples/Devices in this Kaleidoscope repo.) If you're just getting started with the Keyboardio Model 01, the [introductory docs are here](https://github.com/keyboardio/Kaleidoscope/wiki/Keyboardio-Model-01-Introduction) and the source for the basic firmware package is here: https://github.com/keyboardio/Model01-Firmware. It's probably a good idea to start there, learn how to modify your keymap and maybe turn some modules on or off, and then come back to the full repository when you have more complex changes in mind.
# Getting Started # Getting Started
## Setup the Arduino IDE
If you want to use Kaleidoscope to customize or compile a "sketch" to power a supported keyboard, the fastest way to get started is to use the Arduino IDE. You can find [setup instructions](https://kaleidoscope.readthedocs.io/en/latest/quick_start.html) on [kaleidoscope.readthedocs.io](https://kaleidoscope.readthedocs.io) Setup the Arduino IDE on your system. Make sure you install at least version 1.8.6, since older version may not support all required features.
If you prefer to work from the command line or intend to work on Kaleidscope itself, please follow the instructions below. It's important to note that the Arduino IDE needs the source code laid out in a slightly different arrangement than you'll find in this repository. If you want to use the Arduino IDE, you should follow [these instructions](https://kaleidoscope.readthedocs.io/en/latest/quick_start.html) instead.
# Use git to check out a copy of Kaleidoscope * On Linux, follow the instructions [on the wiki](https://github.com/keyboardio/Kaleidoscope/wiki/Install-Arduino-support-on-Linux).
* On macOS, install using [homebrew](http://brew.sh/) [cask](https://caskroom.github.io/) with `brew cask install arduino` or download the application from [the official website](https://www.arduino.cc/en/Main/Software) and move it to your `/Applications` folder.
1. Open a commandline shell and navigate to where you want to check out Kaleidoscope. ## Get into the right directory
For the purposes of this tutorial, we'll assume your checkout is in a directory called `kaleidoscope` inside a directory called `git` in your home directory.
### macOS
```sh ```sh
$ mkdir ${HOME}/git mkdir -p $HOME/Documents/Arduino/hardware
$ cd ${HOME}/git cd $HOME/Documents/Arduino/hardware
``` ```
2. Download the latest version of Kaleidoscope ### Linux
```sh ```sh
$ git clone https://github.com/keyboardio/Kaleidoscope mkdir -p $HOME/Arduino/hardware
cd $HOME/Arduino/hardware
``` ```
3. Ask Kaleidoscope to install the compiler toolchain, arduino-cli and platform support ### Windows (Assuming cmd)
```sh ```sh
$ cd ${HOME}/git/Kaleidoscope mkdir %userprofile%\Documents\Arduino\hardware
$ make setup cd %userprofile%\Documents\Arduino\hardware
``` ```
4. Tell your shell where to find your Kaleidoscope installation. This example is for bash. If you're using another shell, consult the shell's documentation for instructions about how to set an environment variable
### Install the libraries and hardware definitions
## Clone the hardware definitions
Because git for Windows doesn't always have symlink support enabled by default, you may need to enable it for Kaleidoscope. To do that, add '-c core.symlinks=true' to your commandline. On all other platforms, that option is safe, but not necessary, as symlinks should work by default.
```sh ```sh
$ export KALEIDOSCOPE_DIR=${HOME}/git/Kaleidoscope git clone -c core.symlinks=true --recursive https://github.com/keyboardio/Kaleidoscope-Bundle-Keyboardio.git keyboardio
$ echo "export KALEIDOSCOPE_DIR=${HOME}/git/Kaleidoscope" >> ${HOME}/.bash_profile
``` ```
5. Build the Kaleidoscope Firmware for your keyboard ## Build the Kaleidoscope Firmware for your keyboard
(This part assumes you're building firmware for the Keyboardio Atreus) (This part assumes you're building firmware for the Keyboardio Model 01)
```sh ```sh
$ cd examples/Devices/Keyboardio/Atreus # Go to your device firmware directory
cd keyboardio/avr/libraries/Model01-Firmware
$ make compile # Build your firmware!
``` make
5. Install your firmware
```sh # Install your firmware
$ make flash make flash
``` ```
<3 jesse <3 jesse
[![Build
Status](https://travis-ci.org/keyboardio/Kaleidoscope.svg?branch=master)](https://travis-ci.org/keyboardio/Kaleidoscope)

@ -1,296 +0,0 @@
#!/usr/bin/env perl
###################
################### This tool is horribly written.
################### It is a bloody hack.
################### It should be refactored. But it seems to kind of work
###################
###################
my $executed_as = join( ' ', @ARGV );
use warnings;
use strict;
use File::Temp qw/tempdir/;
use Cwd qw/abs_path cwd/;
use JSON::PP;
use Getopt::Long;
my @ARCHES = (qw(avr gd32));
my $platforms_template = {
avr => {
'toolsDependencies' => [
{
"packager" => "arduino",
"name" => "avr-gcc",
"version" => "7.3.0-atmel3.6.1-arduino7"
},
{
"packager" => "arduino",
"name" => "avrdude",
"version" => "6.3.0-arduino17"
}
],
'name' => "Kaleidoscope keyboards",
'boards' => [
{ 'name' => 'Keyboardio Model 01' },
{ 'name' => 'Keyboardio Atreus' }
],
'architecture' => 'avr',
'category' => 'Contributed',
'help' => {
'online' => 'https://community.keyboard.io'
}
},
gd32 => {
'name' => "Kaleidoscope keyboards (GD32)",
'architecture' => 'gd32',
'toolsDependencies' => [
{
'name' => 'xpack-arm-none-eabi-gcc',
'packager' => 'keyboardio',
'version' => '9.3.1-1.3'
},
{
'packager' => 'keyboardio',
'version' => '0.11.0-1',
'name' => 'xpack-openocd'
},
{
'name' => 'dfu-util',
'version' => '0.10.0-arduino1',
'packager' => 'keyboardio'
}
],
'version' => '0.0.1',
'name' => 'Kaleidoscope Keyboards (GD32)',
'boards' => [
{
'name' => 'Keyboardio Model 100'
}
],
'category' => 'Contributed',
'help' => {
'online' => 'https://community.keyboard.io'
}
}
};
my $version = '';
my $tag = 'master';
my $gd32_tag = 'main';
my $kaleidoscope_tag = 'master';
my $working_dir = tempdir( CLEANUP => 0 );
my $index_filename_slug = 'keyboardio';
my $output_dir = $working_dir . '/boards_manager_repo';
my $builds_dir = $output_dir . '/builds';
my $only_latest_platform = 0;
my $bundle_repo = 'https://github.com/keyboardio/Kaleidoscope-Bundle-Keyboardio';
my $gd32_core_repo = 'https://github.com/keyboardio/ArduinoCore-GD32-Keyboardio';
my $package_repo = 'https://github.com/keyboardio/boardsmanager';
my $push_package_repo = 0;
GetOptions(
"bundle-tag=s" => \$tag,
"arch-gd32-tag=s" => \$gd32_tag,
"kaleidoscope-tag=s" => \$kaleidoscope_tag,
"bundle-repo=s" => \$bundle_repo,
"output-repo=s" => \$package_repo,
"index-filename-slug=s" => \$index_filename_slug,
"version=s" => \$version,
"only-one-platform-revision" => \$only_latest_platform,
"push" => \$push_package_repo
) or die("Error in command line arguments\n");
my $index_filename = 'package_' . $index_filename_slug . '_index.json';
my $main_branch_name = 'main';
if ( $version eq '' && $tag =~ /^v(\d.*)$/ ) {
$version = $1;
}
my $checkout_dir = 'kaleidoscope-checkout';
my $build_base_url = $package_repo . "/raw/$main_branch_name/builds";
$build_base_url =~ s|ssh://git@|https://|;
print "Working directory: $working_dir\n";
chdir($working_dir);
clone_repos( $tag, $gd32_tag, $bundle_repo, $gd32_core_repo, $checkout_dir, $package_repo );
chdir( $working_dir . "/" . $checkout_dir );
if ($only_latest_platform) {
`rm -rf $builds_dir/*`;
}
foreach my $arch (@ARCHES) {
remove_unneeded_files($arch);
# Get the specified Kaleidoscope build and all its plugins as libraries
# into the checkout
chdir("$working_dir/$checkout_dir/$arch/libraries/Kaleidoscope");
checkout_kaleidoscope($kaleidoscope_tag);
lift_plugins_to_arduino_libs();
set_plugin_versions( $working_dir, $checkout_dir, $arch, $version );
chdir($working_dir);
my $release_dir = "Kaleidoscope-$arch-$version";
my $filename = $release_dir . ".tar.bz2";
build_archive( $arch, $checkout_dir, $release_dir, $filename );
deploy_build( $builds_dir, $filename );
}
update_index_file( $output_dir, $index_filename );
chdir("$output_dir");
commit_results($index_filename);
finalize_action($push_package_repo);
exit 0;
sub set_plugin_versions {
my $working_dir = shift;
my $checkout_dir = shift;
my $arch = shift;
my $version = shift;
chdir("$working_dir/$checkout_dir/$arch/libraries");
`perl -pi -e's/version=0.0.0/version=$version/' */library.properties`;
}
sub clone_repos {
my $tag = shift;
my $gd32_tag = shift;
my $bundle_repo = shift;
my $gd32_repo = shift;
my $checkout_dir = shift;
my $package_repo = shift;
`git clone --depth=1 --quiet --recurse-submodules --branch $tag $bundle_repo $checkout_dir`;
`git clone --depth=1 --quiet --recurse-submodules --branch $gd32_tag $gd32_repo $checkout_dir/gd32`;
`git clone $package_repo $output_dir`;
}
sub checkout_kaleidoscope {
my $tag = shift;
`git checkout --quiet $tag`;
}
sub lift_plugins_to_arduino_libs {
if ( -d 'plugins' ) {
chdir("plugins");
# move the plugins to be fullfledged arduino libraries
`mv * ../../`;
}
}
sub update_index_file {
my $output_dir = shift;
my $index_filename = shift;
my $json = JSON::PP->new->allow_nonref;
local $/;
my $index_path = $output_dir . '/' . $index_filename;
open( my $fh, '<', $index_path ) || die "Could not open $index_path $!";
my $json_text = <$fh>;
my $data = decode_json($json_text);
#my $this_packager = $data->{'packages'}->[0]->{'name'};
#foreach my $tool (@{$platforms_template->{'gd32'}->{'toolsDependencies'}}) {
# # we need to set the packager of these tools to ourself, since that's what arduino expects
# $tool->{"packager"} = $this_packager;
# }
if ($only_latest_platform) {
@{ $data->{'packages'}->[0]->{'platforms'} } =
( $platforms_template->{'avr'}, $platforms_template->{'gd32'} );
}
else {
push @{ $data->{'packages'}->[0]->{'platforms'} },
$platforms_template->{'avr'},
$platforms_template->{'gd32'};
}
my $json_out = $json->canonical->pretty->encode($data);
open( my $out_fh, '>', $index_path );
print $out_fh $json_out;
close($out_fh);
}
sub commit_results {
my $index_filename = shift;
`git add $index_filename`;
`git add builds`;
`git commit -a -m 'Built by $executed_as'`;
}
sub remove_unneeded_files {
my $arch = shift;
my @bundle_dirs_to_remove = (
"etc",
"doc",
$arch . '/bootloaders/*/lufa',
"$arch/libraries/Kaleidoscope/testing",
"$arch/libraries/Kaleidoscope/tests",
"$arch/libraries/Kaleidoscope/docs"
);
foreach my $dir_to_remove (@bundle_dirs_to_remove) {
print "rm -rf $dir_to_remove\n";
`rm -rf $dir_to_remove`;
}
}
sub deploy_build {
my $builds_dir = shift;
my $filename = shift;
`mkdir -p $builds_dir`;
`cp $filename $builds_dir/`;
}
sub build_archive {
my $arch = shift;
my $checkout_dir = shift;
my $release_dir = shift;
my $filename = shift;
`mv $checkout_dir/$arch $checkout_dir/$release_dir`;
; # A hack to get consistent naming with the old setup
`find "$checkout_dir/$release_dir" -name .git |xargs rm -rf`;
`tar cjvf $filename -C $checkout_dir/ $release_dir `;
my $sha256 = `sha256sum $filename | cut -d' ' -f 1 `;
chomp($sha256);
die "There was a problem generating the sha256" unless ($sha256);
my $size = -s $filename;
$platforms_template->{$arch}->{'archiveFileName'} = $filename;
$platforms_template->{$arch}->{'url'} = $build_base_url . '/' . $filename;
$platforms_template->{$arch}->{'version'} = $version;
$platforms_template->{$arch}->{'checksum'} = 'SHA-256:' . $sha256;
$platforms_template->{$arch}->{'size'} = $size;
}
sub finalize_action {
my $do_push = shift;
if ($do_push) {
`git push`;
}
else {
print "Now, you need to cd to $output_dir check the content and push it\n";
}
}

@ -1,13 +0,0 @@
#!/usr/bin/env bash
: "${KALEIDOSCOPE_DIR:=$(pwd)}"
cd "${KALEIDOSCOPE_DIR}" || exit 1
ERROR_COUNT=0
while read -r SCRIPT; do
shellcheck "${SCRIPT}"
(( ERROR_COUNT += $? ))
done < <(grep -E -n -r -l '(env (ba)?sh)|(/bin/(ba)?sh)' "${KALEIDOSCOPE_DIR}/bin")
exit $ERROR_COUNT

@ -1,110 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
# Copyright (c) 2022 Michael Richters <gedankenexperimenter@gmail.com>
# This is free and unencumbered software released into the public domain.
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
# For more information, please refer to <http://unlicense.org/>
# ------------------------------------------------------------------------------
"""Utilities shared by Kaleidoscope code maintenance tools."""
import logging
import os
import subprocess
import sys
from contextlib import contextmanager
# ==============================================================================
@contextmanager
def cwd(temp_wd):
"""Execute code in a different working directory
Parameters:
`temp_wd` (`str`): The name of a directory to (temporarily) change to
Change the current working directory, then automatically restore the previous working
directory when done. Invoke `cwd()` like this:
```py
with cwd(temp_wd):
...
```
"""
old_wd = os.getcwd()
os.chdir(temp_wd)
try:
yield
finally:
os.chdir(old_wd)
# ==============================================================================
def split_on_newlines(string):
"""Split the string using newlines as the separator."""
return string.splitlines()
# ------------------------------------------------------------------------------
def split_on_nulls(string):
"""Split the input string using NULL characters as the separator."""
targets = [_ for _ in string.split('\0') if _ != '']
return targets or []
# ==============================================================================
def setup_logging(loglevel):
"""Set up basic logging."""
logformat = "%(message)s"
logging.basicConfig(
level=loglevel,
stream=sys.stdout,
format=logformat,
datefmt="",
)
return logging.getLogger()
# ==============================================================================
def check_git_diff():
"""Check for unstaged changes with `git diff`
Returns: a list of the names of files with unstaged changes
This check isn't perfect, because it can give false positives (if there are unrelated
unstaged changes).
"""
git_diff_cmd = ['git', 'diff', '-z', '--exit-code', '--name-only']
changed_files = []
proc = subprocess.run(git_diff_cmd, capture_output=True)
if proc.returncode != 0:
changed_files = split_on_nulls(proc.stdout.decode('utf-8'))
return changed_files
# ==============================================================================
if __name__ == "__main__":
sys.exit(1)

6921
bin/cpplint.py vendored

File diff suppressed because it is too large Load Diff

@ -1,11 +0,0 @@
#!/bin/sh
export EXAMPLE="$1"
EXNAME=$(echo "${EXAMPLE}" |cut -c 3-)
cd examples ||exit; \
install -d dirname "${EXAMPLE}"
printf "# %s\n\`\`\` c++\n", "${EXNAME}" > "${EXAMPLE}.md"
cat ../../examples/"${EXAMPLE}" >> "${EXAMPLE}.md"
printf "\n\`\`\`" >> "${EXAMPLE}.md"

@ -0,0 +1,24 @@
#!/usr/bin/env perl
use warnings;
use strict;
#
# Scan all USB devices to find the Model 01's modem device number.
#
my @output = qx(/usr/sbin/usbconfig show_ifdrv);
my $serial_port_number;
foreach my $line (@output) {
chomp $line;
next unless $line =~ m/umodem(\d+):.*Keyboardio Model 01/;
$serial_port_number = $1;
}
die "Can't find Model 01" unless defined($serial_port_number);
my $serial_port_name = "/dev/cuaU$serial_port_number";
die "Missing serial port at $serial_port_name" unless -e $serial_port_name;
print "$serial_port_name\n";
exit 0;

@ -0,0 +1,92 @@
#!/usr/bin/env perl
# find-device-port-linux-udev - Kaleidoscope helper tool
# Copyright (C) 2017-2018 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 <http://www.gnu.org/licenses/>.
use warnings;
use strict;
use FindBin qw{$Bin};
die "Usage: $0 VID PID [-v]\n" unless @ARGV >= 2;
my $vid = shift @ARGV;
my $pid = shift @ARGV;
my $verbose = shift @ARGV if (@ARGV);
my $prefix = '/dev/serial/by-id/';
my @paths = `ls $prefix`;
my %devices;
my @log;
sub debug {
if ($verbose) {
print STDERR @_;
} else {
push @log, @_;
}
}
sub print_warning {
print STDERR @_;
}
debug "Looking for USB device with vid=$vid and pid=$pid\n";
for my $path (@paths) {
chomp($path);
debug "Examining $path\n";
debug " not symlink\n" unless -l $prefix . $path;
next unless -l $prefix . $path;
my @data = `udevadm info -q property --name=${prefix}${path}`;
for my $line (@data) {
chomp($line);
my ( $key, $val ) = split( /=/, $line, 2 );
$devices{$path}{$key} = $val;
}
if ( hex $devices{$path}{'ID_VENDOR_ID'} != hex $vid ) {
debug " ID_VENDOR_ID $devices{$path}{'ID_VENDOR_ID'} != $vid\n";
next;
}
if ( hex $devices{$path}{'ID_MODEL_ID'} != hex $pid ) {
debug " ID_MODEL_ID $devices{$path}{'ID_MODEL_ID'} != $pid\n";
next;
}
debug " Found keyboard!\n";
if ( $devices{$path}{'ID_MM_DEVICE_IGNORE'} ) {
debug " ID_MM_DEVICE_IGNORE is set - good!\n";
}
if ( $devices{$path}{'ID_MM_CANDIDATE'}) {
my $rules = "$Bin/../etc/60-kaleidoscope.rules";
print_warning <<EOWARN
WARNING: your udev rules are currently configured to suggest
that your keyboard is suitable for use by ModemManager. This
means that there is a risk of ModemManager interfering. To avoid
this, copy
$rules
to /etc/udev/rules.d
EOWARN
}
print $devices{$path}{DEVNAME};
exit(0);
}
#debug("ERROR: I couldn't find a USB device matching the keyboard's USB Vendor and Device IDs\n");
#print_warning(join("\n",@log));

@ -0,0 +1,113 @@
#!/usr/bin/env perl
# Based on listArduinos.pl from https://github.com/todbot/usbSearch (License: MIT)
# Original (C) 2012, Tod E. Kurt, http://todbot.com/blog/
# This version by Michael Richters <gedankenexperimenter@gmail.com> and Jesse Vincent <jesse@keyboard.io>
use warnings;
use strict;
my $vid = shift @ARGV;
my $pid = shift @ARGV;
if (!defined $vid || !defined $pid) {
die "$0 has two required parameters, VID and PID";
}
# ioreg might be more machine-readable than system_profiler, but I haven't been able to
# get it to produce useful output
my @output = qx(/usr/sbin/system_profiler SPUSBDataType 2> /dev/null);
my $serial = "";
my $location = "";
my $output = join('', @output);
my @stanzas = split(/\n\n/, $output);
foreach my $stanza (@stanzas) {
if ($stanza =~ /Product ID: ${pid}/ && $stanza =~ /Vendor ID: ${vid}/) {
if ($stanza =~ /Serial Number: (.*?)$/m) {
$serial = $1;
}
if ($stanza =~ /Location ID: (.*?)$/m) {
$location = $1;
}
if ($serial) {
try_for_raw_serialnum($serial);
}
if ($location) {
try_for_location_id($location);
}
if ($serial) {
try_for_sn_prefix($serial);
}
}
}
sub try_for_raw_serialnum {
my $sn = shift;
my $serial_port_name = "/dev/cu.usbmodem" . $sn;
exit_with_port_if_exists($serial_port_name);
# High Sierra sometimes has a mismatch between the serial number and the device
# filename. I'm not sure why, but system_profiler has a serial number ending in "E",
# whereas the device filename ends in "1". In fact, when I change HID.getShortName()
# to return "kbio02", the final character is replaced with a "1".
if ($serial_port_name =~ /\d$/) {
chop $serial_port_name;
exit_with_port_if_exists($serial_port_name . "1");
} else {
# If the serial port name doesn't end with a digit, try -appending- rather than replacing
# the last character of the port name
exit_with_port_if_exists($serial_port_name . "1");
# and if that didn't work, try replacing the last character with a "1" anyway.
# Jason Koh reports that he saw this behavior as required on Catalina in May 2020.
chop $serial_port_name;
exit_with_port_if_exists($serial_port_name . "1");
}
}
sub try_for_location_id {
my $location_id = shift;
# macOS truncates the string of "0"s from the right of the location id.
# Here, also, the final character is an appended "1", so if macOS ever stops doing that,
# this will need an update, as well.
if ($location_id =~ /0x(\d+?)0*\b/) {
my $loc = $1;
exit_with_port_if_exists("/dev/cu.usbmodem" . $loc . "1");
}
}
sub try_for_sn_prefix {
my $sn = shift;
# If macOS has appended 'E', take it off to maximise our chances of a match.
$sn =~ s/E$//;
# If none of the above tests succeeds, just list the directory and see if there are any
# files that have the device shortname that we expect:
foreach my $line (qx(ls /dev/cu.usbmodem*)) {
if ($line =~ /${sn}/) {
chomp $line;
print $line;
exit 0;
}
}
}
sub exit_with_port_if_exists {
my $serial_port_name = shift;
if (-e $serial_port_name) {
print $serial_port_name;
exit 0;
}
}

@ -0,0 +1,41 @@
# Usage:
#
# > find-device-port-cygwin.ps1 '1209' '2300' -Format COM
# COM7
# > find-device-port-cygwin.ps1 '1209' '2300' -Format WSL
# /dev/ttyS7
# > find-device-port-cygwin.ps1 '1209' '2300' -Format Cygwin
# /dev/ttyS6
Param(
[string]$VendorID,
[string]$ProductID, # Careful; $PID is a different builtin
[ValidateSet('COM','Cygwin','WSL')][string]$Format
)
$DeviceParametersRegKey = @(Get-ChildItem -ErrorAction SilentlyContinue -Recurse 'HKLM:\SYSTEM\CurrentControlSet\Enum' |
Where-Object Name -match "VID_$VendorID&PID_$ProductID" |
Where-Object Property -eq PortName)
if ($DeviceParametersRegKey.Count -eq 0) {
throw "Could not find any devices matching VID $VendorID and PID $ProductID which were mapped to a COM port"
}
if ($DeviceParametersRegKey.Count -ge 2) {
throw "More than one devices matching VID $VendorID and PID $ProductID were found mapped to a COM port"
}
# This will be of form 'COM6'
$COMPortName = ($DeviceParametersRegKey | Get-ItemProperty).PortName
$COMPortNumber = [int]$COMPortName.Substring(3)
if ($Format -eq 'COM') {
$Output = $COMPortName
} elseif ($Format -eq 'WSL') {
$Output = "/dev/ttyS$COMPortNumber"
} elseif ($Format -eq 'Cygwin') {
$CygwinPortNumber = $COMPortNumber - 1
$Output = "/dev/ttyS$CygwinPortNumber"
}
# "-NoNewline" below is important to prevent bash from seeing an extra trailing '\r'
Write-Host -NoNewline $Output

@ -0,0 +1,55 @@
#!/usr/bin/env bash
# find-filename-conflicts - Finds cpp files with conflicting filenames
# Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
## When building Kaleidoscope, the compiled object files are linked together
## into a static archive. This static archive has a very simple structure, and
## only stores filenames, not paths, not even relative ones. As such, we can't
## have files with the same name, because they will conflict, and one will
## override the other.
##
## To avoid this situation, this script will find all cpp source files (we don't
## need to care about header-only things, those do not result in an object
## file), and will comb through them to find conflicting filenames.
##
## If a conflict is found, it will print all files that share the name, and will
## exit with an error at the end. It does not exit at the first duplicate, but
## will find and print all of them.
##
## If no conflict is found, the script just prints its status message and exits
## with zero.
set -e
FILE_LIST="$(find src -name '*.cpp' | sed -e 's,\(\(.*\)/\([^/]*\)\),\3 \1,')"
exit_code=0
echo -n "Looking for conflicting filenames... "
for f in $(echo "${FILE_LIST}" | cut -f1 -d" "); do
count=$(echo "${FILE_LIST}" | grep -c "^${f}")
if [ "$count" -gt 1 ]; then
echo >&2
echo " Conflict found for ${f}: " >&2
echo "${FILE_LIST}" | grep "${f}" | cut -d" " -f2 | sed -e 's,^, ,' >&2
exit_code=1
fi
done
if [ "${exit_code}" -eq 0 ]; then
echo "done."
fi
exit ${exit_code}

@ -1,96 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
# Copyright (c) 2022 Michael Richters <gedankenexperimenter@gmail.com>
# This is free and unencumbered software released into the public domain.
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
# For more information, please refer to <http://unlicense.org/>
# ------------------------------------------------------------------------------
"""When building Kaleidoscope, the compiled object files are linked together
into a static archive. This static archive has a very simple structure, and only
stores filenames, not paths, not even relative ones. As such, we can't have
files with the same name, because they will conflict, and one will override the
other.
To avoid this situation, this script will find all cpp source files (we don't
need to care about header-only things, those do not result in an object file),
and will comb through them to find conflicting filenames.
If a conflict is found, it will print all files that share the name, and will
exit with an error at the end. It does not exit at the first duplicate, but will
find and print all of them.
If no conflict is found, the script just prints its status message and exits
with zero."""
import os
import re
import sys
cpp_regex = re.compile('.*\.cpp')
def find_duplicates(root):
"""Search for files with the same basename, but in different directories in
the tree under <root>. Prints a message for each conflict found, and
returns a count of the number of non-unique basenames."""
# Search the specified tree for matching basenames:
basenames = {}
for dir_path, dirs, files in os.walk(root):
for file_name in files:
if cpp_regex.match(file_name):
if file_name not in basenames:
basenames[file_name] = []
basenames[file_name].append(dir_path)
conflict_count = 0
for file_name, dirs in basenames.items():
# Prune unique basenames from the dict:
if len(dirs) <= 1:
continue
conflict_count += 1
# Print info about basenames with conflicts:
print(f"Conflict found for file name '{file_name}':")
for root in dirs:
path = os.path.join(root, file_name)
print(f' -> {path}')
return conflict_count
def main(args):
print('Searching for conflicting filenames...')
exit_code = 0
for path in args:
exit_code += find_duplicates(path)
if exit_code != 0:
sys.exit(exit_code)
print('No filename conflicts found.')
if __name__ == "__main__":
main(sys.argv[1:])

@ -1,43 +0,0 @@
#!/usr/bin/env bash
# In case it hasn't been set, assume we're being run from the root of the local
# Kaleidoscope repository.
: "${KALEIDOSCOPE_DIR:=$(pwd)}"
# This variable is a git ref that should point to the current master branch of
# the primary Kaleidoscope repository. In most cases, this would be
# `origin/master`.
: "${KALEIDOSCOPE_MERGE_BASE:=origin/master}"
# Don't do anything if the working tree has unstaged changes, to avoid
# unintentional combining of contentful changes with formatting changes.
if ! git diff -z --exit-code --quiet; then
echo "Working tree has unstaged changes; aborting."
exit 1
fi
# Run git-diff so we only run IWYU on files that differ from `master`. This
# isn't necessarily what we want, but if the current branch has been rebased, it
# shouldn't touch any extra files.
git diff -z --name-only "${KALEIDOSCOPE_MERGE_BASE}" -- src plugins \
| "${KALEIDOSCOPE_DIR}/bin/iwyu.py" -z -v
# After running it on Kaleidoscope source files, run it on the test simulator,
# which requires some additional include dirs.
git diff -z --name-only "${KALEIDOSCOPE_MERGE_BASE}" -- testing \
| "${KALEIDOSCOPE_DIR}/bin/iwyu.py" \
-z -v \
-I="${KALEIDOSCOPE_DIR}" \
-I="${KALEIDOSCOPE_DIR}/testing/googletest/googlemock/include" \
-I="${KALEIDOSCOPE_DIR}/testing/googletest/googletest/include"
# Always run clang-format after IWYU, because they have different indentation
# rules for comments added by IWYU.
git diff -z --name-only "${KALEIDOSCOPE_MERGE_BASE}" -- src plugins testing \
| "${KALEIDOSCOPE_DIR}/bin/format-code.py" \
-z -v \
--exclude-dir='testing/googletest' \
--exclude-file='generated-testcase.cpp' \
--force \
--check \
--verbose

@ -1,63 +0,0 @@
#!/usr/bin/env bash
# focus-send - Trivial Focus testing tool
# Copyright (C) 2018-2022 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 <http://www.gnu.org/licenses/>.
set -e
OS=$(uname -s)
# igncr absorbs CR from Focus CRLF line endings
# -echo is needed because raw doesn't turn it off on Linux
STTY_ARGS="9600 raw igncr -echo"
case ${OS} in
Linux)
DEVICE="${DEVICE:-/dev/ttyACM0}"
;;
Darwin)
# bash on macOS has a bug that randomly drops serial input
if [ -n "$BASH_VERSION" ] && [ -x /bin/dash ]; then
# Prevent loop in case someone exported it
export -n BASH_VERSION
exec /bin/dash "$0" "$@"
fi
DEVICE="${DEVICE:-/dev/cu.usbmodemCkbio01E}"
;;
*)
echo "Error Unknown OS : ${OS}" >&2
exit 1
;;
esac
# Redirect prior to running stty, because macOS sometimes resets termios
# state upon last close of a terminal device.
exec < "${DEVICE}"
# shellcheck disable=SC2086 # intentional word splitting
stty $STTY_ARGS
read_reply () {
while read -r line; do
if [ "${line}" = "." ]; then
break
fi
echo "${line}"
done
}
# Flush any invalid commands out of input buffer.
# This could happen after a failed upload.
echo ' ' > "${DEVICE}"
read_reply > /dev/null
echo "$@" >"${DEVICE}"
read_reply

@ -0,0 +1,45 @@
#!/usr/bin/env bash
# focus-test - Trivial Focus testing tool
# Copyright (C) 2018 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 <http://www.gnu.org/licenses/>.
set -e
OS=$(uname -s)
case ${OS} in
Linux)
DEVICE="${DEVICE:-/dev/ttyACM0}"
stty -F "${DEVICE}" 9600 raw -echo
;;
Darwin)
DEVICE="${DEVICE:-/dev/cu.usbmodemCkbio01E}"
stty -f "${DEVICE}" 9600 raw -echo
;;
*)
echo "Error Unknown OS : ${OS}" >&2
exit 1
;;
esac
exec 3<"${DEVICE}"
echo "$@" >"${DEVICE}"
while read -r line <&3; do
line="$(echo -n "${line}" | tr -d '\r')"
if [ "${line}" == "." ]; then
break
fi
echo "${line}"
done

@ -1,275 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
# Copyright (c) 2022 Michael Richters <gedankenexperimenter@gmail.com>
# This is free and unencumbered software released into the public domain.
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
# For more information, please refer to <http://unlicense.org/>
# ------------------------------------------------------------------------------
"""This script runs clang-format on a Kaleidoscope repository."""
import argparse
import logging
import os
import re
import shutil
import subprocess
import sys
sys.dont_write_bytecode = True
from common import check_git_diff, setup_logging, split_on_newlines, split_on_nulls
# ==============================================================================
def parse_args(args):
"""Parse command line parameters
Args:
args (list[str]): command line parameters as list of strings
(for example ``["--help"]``).
Returns:
:obj:`argparse.Namespace`: command line parameters namespace
"""
parser = argparse.ArgumentParser(
description="""
Recursively search specified directories and format source files with
clang-format. By default, it operates on Arduino C++ source files with
extensions: *.{cpp,h,hpp,inc,ino}.""")
parser.add_argument(
'-q',
'--quiet',
dest='loglevel',
action='store_const',
const=logging.ERROR,
default=logging.WARNING,
help="""
Suppress output except warnings and errors.""",
)
parser.add_argument(
'-v',
'--verbose',
action='store_const',
dest='loglevel',
const=logging.INFO,
help="""
Output verbose debugging information.""",
)
parser.add_argument(
'-d',
'--debug',
action='store_const',
dest='loglevel',
const=logging.DEBUG,
help="""
Save output from `include-what-you-use` for processed files beside the originals, with
a '.iwyu' suffix, for debugging purposes.""",
)
parser.add_argument(
'-X',
'--exclude-dir',
action='append',
dest='exclude_dirs',
default=[],
metavar="<path>",
help="""
Exclude dir from search (path relative to the pwd)""",
)
parser.add_argument(
'-x',
'--exclude-file',
action='append',
dest='exclude_files',
default=[],
metavar="<file>",
help="""
Exclude <file> (base name only, not a full path) from formatting""",
)
parser.add_argument(
'-r',
'--regex',
dest='regex',
default=r'\.(cpp|h|hpp|inc|ino)$',
metavar="<regex>",
help="""
Regular expression for matching source file names""",
)
parser.add_argument(
'-z',
'-0',
action='store_const',
dest='input_splitter',
const=split_on_nulls,
default=split_on_newlines,
help="""
When reading target filenames from standard input, break on NULL characters instead
of newlines.""",
)
parser.add_argument(
'-f',
'--force',
action='store_true',
help="""
Format code even if there are unstaged changes""",
)
parser.add_argument(
'--check',
action='store_true',
help="""
Check for changes after formatting by running `git diff --exit-code`. If there are any
changes after formatting, a non-zero exit code is returned.""",
)
parser.add_argument(
'targets',
metavar="<search_dir>",
nargs='*',
help="""
A list of files and/or directories to search for source files to format.""",
)
return parser.parse_args(args)
# ==============================================================================
def main():
"""Parse command-line arguments and format source files.
"""
# Parse command-line argumets:
opts = parse_args(sys.argv[1:])
# Set up logging system:
setup_logging(opts.loglevel)
# ----------------------------------------------------------------------
# Unless we've been given the `--force` option, check for unstaged changes to avoid
# clobbering any work in progress:
exit_code = 0
if not opts.force:
changed_files = check_git_diff()
if len(changed_files) > 0:
logging.error("Working tree has unstaged changes; aborting")
return 1
# Locate `clang-format` executable:
clang_format_exe = os.getenv('KALEIDOSCOPE_CODE_FORMATTER')
if clang_format_exe is None:
clang_format_exe = shutil.which('clang-format')
logging.debug("Found `clang-format` executable: %s", clang_format_exe)
clang_format_cmd = [clang_format_exe, '-i']
if opts.loglevel <= logging.INFO:
clang_format_cmd.append('--verbose')
# ----------------------------------------------------------------------
# Read targets from command line:
targets = opts.targets
logging.debug("CLI target parameters: %s", targets)
# If stdin is a pipe, read target filenames from it:
if not sys.stdin.isatty():
targets += opts.input_splitter(sys.stdin.read())
logging.debug("All targets: %s", targets)
# Prepare exclusion lists. The file excludes are basenames only, and the dirs get
# converted to absolute path names.
exclude_files = set(opts.exclude_files)
exclude_dirs = set(os.path.abspath(_) for _ in opts.exclude_dirs)
# Convert target paths to absolute, and remove any that are excluded:
target_paths = set(os.path.abspath(_) for _ in targets if _ not in exclude_dirs)
logging.debug("Target paths: %s", target_paths)
# Build separate sets of target files and dirs. Later, we'll search target dirs and add
# matching target files to the files set.
target_files = set()
target_dirs = set()
for t in target_paths:
if os.path.isfile(t):
target_files.add(os.path.abspath(t))
elif os.path.isdir(t):
target_dirs.add(os.path.abspath(t))
logging.debug("Target files after separating: %s", target_files)
logging.debug("Target dirs after separating: %s", target_dirs)
# Remove excluded filenames:
target_files -= set(_ for _ in target_files if os.path.basename(_) in exclude_files)
# Remove files and dirs in excluded dirs:
target_files -= set(_ for _ in target_files for x in exclude_dirs if _.startswith(x))
target_dirs -= set(_ for _ in target_dirs for x in exclude_dirs if _.startswith(x))
# Compile regex for matching files to be formatted:
target_matcher = re.compile(opts.regex)
# Remove target files that don't match the regex:
logging.debug("Target files before matching regex: %s", target_files)
target_files = set(_ for _ in target_files if target_matcher.search(_))
logging.debug("Target files after matching regex: %s", target_files)
# Search target dirs for non-excluded files, and add them to `target_files`:
logging.debug("Searching target dirs: %s", target_dirs)
for path in target_dirs:
for root, dirs, files in os.walk(path):
# Prune excluded dirs
for x in exclude_dirs:
if x in (os.path.join(root, _) for _ in dirs):
dirs.remove(os.path.basename(x))
# Add non-excluded files
for f in files:
if target_matcher.search(f) and f not in exclude_files:
target_files.add(os.path.join(root, f))
if len(target_files) == 0:
logging.error("No target files found; exiting.")
return 1
# Run clang-format on target files:
proc = subprocess.run(clang_format_cmd + sorted(target_files))
if proc.returncode != 0:
logging.error("Error: clang-format returned non-zero status: %s", proc.returncode)
return proc.returncode
else:
logging.info("Finished formatting target files.")
# If we've been asked to check for changes made by the formatter:
if opts.check:
logging.warning('Checking for changes made by the formatter...')
changed_files = check_git_diff()
if len(changed_files) == 0:
logging.warning("No files changed. Congratulations!")
else:
logging.warning("Found files with changes after formatting:")
exit_code = 1
for f in changed_files:
logging.warning(" %s", f)
return exit_code
# ==============================================================================
if __name__ == "__main__":
try:
sys.exit(main())
except KeyboardInterrupt:
logging.info("Aborting")
sys.exit(1)

@ -1,12 +0,0 @@
#!/usr/bin/env bash
: "${KALEIDOSCOPE_DIR:=$(pwd)}"
cd "${KALEIDOSCOPE_DIR}" || exit 1
: "${VERBOSE:=}"
"${KALEIDOSCOPE_DIR}/bin/format-code.py" \
--exclude-dir 'testing/googletest' \
--exclude-file 'generated-testcase.cpp' \
--verbose \
src plugins testing

@ -1,508 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
# Copyright (c) 2022 Michael Richters <gedankenexperimenter@gmail.com>
# This is free and unencumbered software released into the public domain.
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
# For more information, please refer to <http://unlicense.org/>
# ------------------------------------------------------------------------------
"""Run `include-what-you-use` on Kaleidoscope sources
This is a script for maintenance of the headers included in Kaleidoscope source
files. It is not currently possible to run this automatically on all
Kaleidoscope source files, because of the peculiarities therein. It uses
llvm/clang to determine which headers should be included in a given file, but
there's no avr-clang, so we're limited to using the virtual hardware device.
It takes any number of source files as its input, examines them and updates the
list of header `#include` directives.
It is safe to run on most Kaleidoscope source files, and a good idea to run it
on new ones (after staging them, so you can easily see what changes have been
made).
"""
# Example invocation:
# $ git ls-files -m | grep '\.\(h\|cpp\)' | bin/iwyu.py
import argparse
import glob
import logging
import os
import re
import shlex
import shutil
import subprocess
import sys
sys.dont_write_bytecode = True
from common import cwd, setup_logging, split_on_newlines, split_on_nulls
# ==============================================================================
def parse_args(args):
"""Parse command line parameters
Args:
args (list[str]): command line parameters as list of strings
(for example ``["--help"]``).
Returns:
:obj:`argparse.Namespace`: command line parameters namespace
"""
parser = argparse.ArgumentParser(
description="""
Run `include-what-you-use` on source files given as command-line arguments and/or read
from standard input. When reading target filenames from standard input, they should be
either absolute or relative to the current directory, and each line of input (minus the
line-ending character(s) is treated as a filename.""")
parser.add_argument(
'-q',
'--quiet',
dest='loglevel',
action='store_const',
const=logging.ERROR,
default=logging.WARNING,
help="""
Suppress output except warnings and errors.""",
)
parser.add_argument(
'-v',
'--verbose',
action='store_const',
dest='loglevel',
const=logging.INFO,
help="""
Output verbose debugging information.""",
)
parser.add_argument(
'-d',
'--debug',
action='store_const',
dest='loglevel',
const=logging.DEBUG,
help="""
Save output from `include-what-you-use` for processed files beside the originals, with
a '.iwyu' suffix, for debugging purposes.""",
)
parser.add_argument(
'-r',
'--regex',
action='store',
dest='regex',
default=r'\.(h|cpp)$',
help="""
A regular expression for matching filenames Only the basename of the file is
matched, and the regex is only used when searching a directory for files to process,
not on target filenames specified in arguments or read from standard input.""",
)
parser.add_argument(
'-i',
'--ignores_file',
action='store',
dest='ignores_file',
default='.iwyu_ignore',
metavar='<ignores_file>',
help="""
The name of a file (relative to KALEIDOSCOPE_DIR) that contains a list of glob patterns
that will be ignored when a target directory is searched for filenames that match
<regex>.""",
)
parser.add_argument(
'-I',
'--include',
action='append',
dest='include_dirs',
metavar='<dir>',
help="""
Add <dir> to the list of directories that will be searched for header files.""",
)
parser.add_argument(
'-z',
'-0',
action='store_const',
dest='input_splitter',
const=split_on_nulls,
default=split_on_newlines,
help="""
When reading target filenames from standard input, break on NULL characters instead
of newlines.""",
)
parser.add_argument(
'--update_comments',
action='store_true',
help="""
Call `include-what-you-use` with `--update_comments` to always rewrite its 'for'
comments regarding which symbols are used.""",
)
parser.add_argument(
'targets',
nargs='*',
metavar="<target>",
help="""
A list of target files and/or directories to search for source files to format. Any
target file will be processed, regardless of the filename. Any target directory will
be recursively searched for files matching the regular expression given by --regex.
Filenames and directories beginning with a '.' will always be excluded from the search,
but can still be processed if specified as a command-line target.""",
)
return parser.parse_args(args)
# ==============================================================================
def main():
"""Main entry point function."""
# Parse command-line arguments:
opts = parse_args(sys.argv[1:])
# Set up logging system:
setup_logging(opts.loglevel)
# ----------------------------------------------------------------------
# Find include-what-you-use:
iwyu = shutil.which('include-what-you-use')
logging.debug("Found `include-what-you-use` executable: %s", iwyu)
iwyu_opts = [
'--no_fwd_decls', # No forward declarations
'--max_line_length=100',
]
if opts.update_comments:
iwyu_opts.append('--update_comments')
# Prepend '-Xiwyu' to each `include-what-you-use` option:
iwyu_opts = [_ for opt in iwyu_opts for _ in ('-Xiwyu', opt)]
# ----------------------------------------------------------------------
# Find fix_includes:
fix_includes = shutil.which('fix_includes.py')
logging.debug("Found `fix_includes` executable: %s", fix_includes)
# ----------------------------------------------------------------------
# Find clang (first checking environment variable):
clang = os.getenv('CLANG_COMPILER')
if clang is None:
clang = shutil.which('clang')
logging.debug("Found `clang` executable: %s", clang)
# Get system include dir from `clang`:
clang_cmd = [clang, '-print-resource-dir']
logging.debug("Running command: `%s`", shlex.join(clang_cmd))
result = subprocess.run(clang_cmd, capture_output=True, check=True)
clang_resource_dir = result.stdout.decode('utf-8').rstrip()
system_include_dir = os.path.join(clang_resource_dir, 'include')
logging.debug("Using system include dir: %s", system_include_dir)
# ----------------------------------------------------------------------
# Get $KALEIDOSCOPE_DIR from enironment, falling back on `pwd`:
kaleidoscope_dir = os.getenv('KALEIDOSCOPE_DIR')
if kaleidoscope_dir is None:
kaleidoscope_dir = os.getcwd()
logging.debug("Using Kaleidoscope dir: %s", kaleidoscope_dir)
kaleidoscope_src_dir = os.path.join(kaleidoscope_dir, 'src')
# Define locations of other dirs to find Arduino libraries:
virtual_hardware_dir = os.path.join(
kaleidoscope_dir, '.arduino', 'user', 'hardware', 'keyboardio', 'virtual')
logging.debug("Using virtual hardware dir: %s", virtual_hardware_dir)
virtual_arduino_core_dir = os.path.join(virtual_hardware_dir, 'cores', 'arduino')
logging.debug("Using virtual arduino core: %s", virtual_arduino_core_dir)
virtual_model01_dir = os.path.join(virtual_hardware_dir, 'variants', 'model01')
logging.debug("Using virtual Model01 dir: %s", virtual_model01_dir)
virtual_keyboardiohid_dir = os.path.join(
virtual_hardware_dir, 'libraries', 'KeyboardioHID', 'src')
logging.debug("Using virtual KeyboardioHID dir: %s", virtual_keyboardiohid_dir)
# ----------------------------------------------------------------------
# Create the long list of options passed to `clang` via `include-what-you-use`.
# First, we tell it we're using C++:
clang_opts = ['-x', 'c++']
# General compiler options:
clang_opts += [
'-c',
'-g',
'-Wall',
'-Wextra',
'-std=gnu++14', # Not `c++14`, because we're using clang, not gcc
'-ffunction-sections',
'-fdata-sections',
'-fno-threadsafe-statics',
'-MMD',
'-Woverloaded-virtual',
'-Wno-unused-parameter',
'-Wno-unused-variable',
'-Wno-ignored-qualifiers',
'-Wno-type-limits',
'-Wno-pragma-once-outside-header',
]
# Variables we define to do a Kaleidoscope build:
defines = [
'KALEIDOSCOPE_VIRTUAL_BUILD=1',
'KEYBOARDIOHID_BUILD_WITHOUT_HID=1',
'USBCON=dummy',
'ARDUINO_ARCH_AVR=1',
'ARDUINO=10607',
'ARDUINO_AVR_MODEL01',
'ARDUINO_ARCH_VIRTUAL',
'USB_VID=0x1209',
'USB_PID=0x2301',
'USB_MANUFACTURER="Keyboardio"',
'USB_PRODUCT="Model 01"',
'KALEIDOSCOPE_HARDWARE_H="Kaleidoscope-Hardware-Keyboardio-Model01.h"',
'TWI_BUFFER_LENGTH=32',
]
clang_opts += ['-D' + _ for _ in defines]
# Directories to search for libraries to include:
includes = [
system_include_dir,
kaleidoscope_src_dir,
virtual_arduino_core_dir,
virtual_model01_dir,
virtual_keyboardiohid_dir,
]
# Include plugin source dirs for plugins that depend on other plugins:
includes += glob.glob(os.path.join(kaleidoscope_dir, 'plugins', '*', 'src'))
# Include dirs specified on the command line, if any:
if opts.include_dirs:
includes += [os.path.abspath(_) for _ in opts.include_dirs]
clang_opts += ['-I' + _ for _ in includes]
# ----------------------------------------------------------------------
# Define the `include-what-you-use` command (sans target files)
iwyu_cmd = [iwyu] + iwyu_opts + clang_opts
logging.debug("Using IWYU command: %s", ' \\\n\t'.join(iwyu_cmd))
fix_includes_cmd = [
fix_includes,
'--nosafe_headers',
'--reorder',
'--separate_project_includes=' + kaleidoscope_src_dir, # Does this help?
]
if opts.update_comments:
fix_includes_cmd.append('--update_comments')
logging.debug("Using `fix_includes` command: %s", ' \\\n\t'.join(fix_includes_cmd))
# ----------------------------------------------------------------------
targets = opts.targets
# If stdin is a pipe, read pathname targets, one per line. This allows us to connect the
# output of `find` to our input conveniently:
if not sys.stdin.isatty():
logging.debug("Reading targets from STDIN...")
targets += opts.input_splitter(sys.stdin.read())
for t in targets:
logging.debug(" Target path: %s", t)
# ----------------------------------------------------------------------
iwyu_ignores_file = os.path.join(kaleidoscope_dir, opts.ignores_file)
ignores = build_ignores_list(iwyu_ignores_file)
# ----------------------------------------------------------------------
regex = re.compile(opts.regex)
# Process source files first, then header files, because a source file might have been
# relying on a header included by its associated header, but which that header does not
# need on its own. In this case, if we process the header first, IWYU won't be able to
# parse the source file, and we'll get an error, but if we do them in the other order,
# the source file will get all the includes it needs before the header removes any of them.
source_files = []
header_files = []
for target_file in (_ for t in targets for _ in build_target_list(t, regex, ignores)):
if target_file.endswith('.cpp') or target_file.endswith('.ino'):
source_files.append(target_file)
else:
header_files.append(target_file)
# If there's an error processing any file, return an error code.
exit_code = 0
for target_file in source_files + header_files:
# Run IWYU and fix_headers:
if not run_iwyu(os.path.relpath(target_file), iwyu_cmd, fix_includes_cmd):
exit_code = 1
return exit_code
# ==============================================================================
def build_target_list(target, regex, ignores):
"""Build the list of target files, starting from a file or directory.
Parameters:
target (str): The name of the file or directory to search
regex (Pattern): A compiled regular expression to match target file names
ignores (list): A list of (absolute) file names to be excluded from the target list
Returns:
targets (list): A list of (absolute) file names to be processed
Given a filename or directory (`path`), returns a list of target filenames to run IWYU
on. Target filenames will all be absolute, but `path` can be relative to the current
directory. All target filenames will match `regex`, and any items in `ignores` will be
excluded. If an element of the `ignores` list is a directory, all files and directories
beneath it will be excluded from the search.
"""
logging.debug("Searching target: %s", target)
# Convert all paths to absolute.
root = os.path.abspath(target)
# If `path` is a file, and it matches `regex`, add it to the target list.
if os.path.isfile(target) and regex.search(target) and root not in ignores:
return [root]
# If the specified path is not valid, just return an empty list.
if not os.path.isdir(target):
logging.error("Error: File not found: %s", target)
return []
# Start with an empty list.
targets = []
# The specified path is a directory, so we search recursively for files
# contained therein that match the specified regular expression.
for path, dirs, files in os.walk(root):
logging.debug("Searching dir: %s", os.path.relpath(path))
# First, ignore all dotfiles (and directories).
for x in (os.path.basename(_)
for _ in ignores + glob.glob(os.path.join(path, '.*'))
if os.path.dirname(_) == path):
if x in dirs:
logging.info("Skipping ignored dir: %s", os.path.join(path, x))
dirs.remove(x)
if x in files:
logging.info("Skipping ignored file: %s", os.path.join(path, x))
files.remove(x)
logging.debug("Files found: %s", ', '.join(files))
# Add all matching files to the list of source files to be formatted.
for f in (_ for _ in files if regex.search(_)):
t = os.path.join(path, f)
logging.debug("Source file found: %s", t)
targets.append(t)
return targets
# ==============================================================================
def build_ignores_list(ignores_file_path):
"""Build a list of files and dirs to exclude from processing by IWYU
Parameters:
ignores_file_path (str): The name of the file to read ignores globs from
Returns:
ignores_list (list): A list of (absolute) file names to exclude from processing
Reads `ignores_file` and expands each line as a glob, returning a list of all the target
file names to be excluded from processing. Each path in the file is treated as a relative
pathname (unless it is already absolute), relative to the directory in which `ignores_file`
is located.
"""
logging.debug("Searching for ignores file: %s", ignores_file_path)
# If the ignores file doesn't exist, return an empty list:
if not os.path.isfile(ignores_file_path):
logging.debug("Ignores file not found")
return []
ignores_list = []
with open(ignores_file_path) as f:
for line in f.read().splitlines():
# Lines starting with `#` are treated as comments.
if line.startswith('#'):
continue
logging.debug("Ignoring files like: %s", line)
ignores_list += glob.glob(line, recursive=True)
# Get the dir of the ignores file so we can construct absolute pathnames.
ignores_file_dir = os.path.dirname(ignores_file_path)
with cwd(ignores_file_dir):
ignores_list[:] = [os.path.abspath(_) for _ in ignores_list]
logging.debug("Ignores list:\n\t%s", "\n\t".join(ignores_list))
return ignores_list
# ==============================================================================
def run_iwyu(source_file, iwyu_cmd, fix_includes_cmd):
"""Run IWYU and fix_includes on a single source file
Parameters:
source_file (str): The name of a file to run IWYU on
iwyu_cmd (list): The command name and options list for `include-what-you-use`
fix_includes_cmd (list): The command name and options list for `fix_headers.py`
Returns:
True on success, False if either IWYU or fix_headers returns an error code
Run `include-what-you-use` on <source_file>, an update that file's header includes by
sending the output to `fix_includes.py`. If either command returns an error code, return
`False`, otherwise return `True`.
"""
logging.info("Fixing headers in file: %s", source_file)
# Run IWYU on <source_file>
iwyu_proc = subprocess.run(iwyu_cmd + [source_file], capture_output=True, check=False)
# If IWYU returns an error, report on it:
if iwyu_proc.returncode != 0:
logging.error("Error: failed to parse file: %s", source_file)
logging.debug("IWYU returned: %s", iwyu_proc.returncode)
logging.debug("STDOUT:\n%s", iwyu_proc.stdout.decode('utf-8'))
logging.debug("STDERR:\n%s", iwyu_proc.stderr.decode('utf-8'))
# In addition to reporting the error, save the output for analysis:
with open(source_file + '.iwyu', 'wb') as f:
f.write(iwyu_proc.stderr)
# Don't run fix_includes if there was an error (or if we've got an old version of IWYU
# that returns bogus exit codes):
return False
# IWYU reports on the associated header of *.cpp files, but if we want to skip processing
# that header, we need to use only the part of the output for the *.cpp file. Fortunately,
# the header is listed first, so we only need to search for the start of the target file's
# section of the output.
n = iwyu_proc.stderr.find(f"\n{source_file} should".encode('utf-8'))
iwyu_stderr = iwyu_proc.stderr[n:]
# Run fix_includes.py, using the output (stderr) of IWYU:
fix_includes_proc = subprocess.run(
fix_includes_cmd, input=iwyu_stderr, capture_output=True, check=False)
# Report any errors returned by fix_includes.py:
if fix_includes_proc.returncode != 0:
logging.error("Error: failed to fix includes for file: %s", source_file)
logging.debug("fix_includes.py returned: %s", fix_includes_proc.returncode)
logging.debug("STDOUT:\n%s", fix_includes_proc.stdout.decode('utf-8'))
logging.debug("STDERR:\n%s", fix_includes_proc.stderr.decode('utf-8'))
return False
# Return true on success, false otherwise:
return True
# ==============================================================================
if __name__ == "__main__":
try:
sys.exit(main())
except KeyboardInterrupt:
logging.info("Aborting")
sys.exit(1)

@ -0,0 +1,723 @@
#!/usr/bin/env bash
# kaleidoscope-builder - Kaleidoscope helper tool
# Copyright (C) 2017-2018 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 <http://www.gnu.org/licenses/>.
set -e
######
###### Build and output configuration
######
absolute_filename() {
echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
}
build_version () {
: "${LIB_PROPERTIES_PATH:="../.."}"
GIT_VERSION="$(cd "${SKETCH_DIR}"; if [ -d .git ]; then echo -n '-g' && git describe --abbrev=4 --dirty --always; fi)"
LIB_VERSION="$(cd "${SKETCH_DIR}"; (grep version= "${LIB_PROPERTIES_PATH}/library.properties" 2>/dev/null || echo version=0.0.0) | cut -d= -f2)${GIT_VERSION}"
}
build_paths() {
# We need that echo because we\re piping to cksum
# shellcheck disable=SC2005
SKETCH_IDENTIFIER="$(echo "${SKETCH_FILE_PATH}" | cksum | cut -d ' ' -f 1)-${SKETCH_FILE_NAME}"
: "${KALEIDOSCOPE_TEMP_PATH:=${TMPDIR:-/tmp}/kaleidoscope-${USER}}"
: "${KALEIDOSCOPE_BUILD_PATH:=${KALEIDOSCOPE_TEMP_PATH}/sketch}"
: "${KALEIDOSCOPE_OUTPUT_PATH:=${KALEIDOSCOPE_TEMP_PATH}/sketch}"
: "${SKETCH_OUTPUT_DIR:=${SKETCH_IDENTIFIER}/output}"
: "${SKETCH_BUILD_DIR:=${SKETCH_IDENTIFIER}/build}"
: "${BUILD_PATH:=${KALEIDOSCOPE_BUILD_PATH}/${SKETCH_BUILD_DIR}}"
: "${OUTPUT_PATH:=${KALEIDOSCOPE_OUTPUT_PATH}/${SKETCH_OUTPUT_DIR}}"
: "${CCACHE_WRAPPER_PATH:=${KALEIDOSCOPE_TEMP_PATH}/ccache/bin}"
: "${CORE_CACHE_PATH:=${KALEIDOSCOPE_TEMP_PATH}/arduino-cores}"
mkdir -p "$CORE_CACHE_PATH"
mkdir -p "$BUILD_PATH"
}
build_filenames () {
: "${OUTPUT_FILE_PREFIX:=${SKETCH_BASE_NAME}-${LIB_VERSION}}"
: "${HEX_FILE_PATH:=${OUTPUT_PATH}/${OUTPUT_FILE_PREFIX}.hex}"
: "${HEX_FILE_WITH_BOOTLOADER_PATH:=${OUTPUT_PATH}/${OUTPUT_FILE_PREFIX}-with-bootloader.hex}"
: "${ELF_FILE_PATH:=${OUTPUT_PATH}/${OUTPUT_FILE_PREFIX}.elf}"
: "${LIB_FILE_PATH:=${OUTPUT_PATH}/${OUTPUT_FILE_PREFIX}.a}"
}
enable_ccache () {
if [ -z "${CCACHE_NOT_SUPPORTED}" ] && [ "$(command -v ccache)" ]; then
if ! [ -d "$CCACHE_WRAPPER_PATH" ]; then
mkdir -p "$CCACHE_WRAPPER_PATH"
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}${C_COMPILER_BASENAME}" ]; then
ln -s "$(command -v ccache)" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}${C_COMPILER_BASENAME}"
fi
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}${CXX_COMPILER_BASENAME}" ]; then
ln -s "$(command -v ccache)" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}${CXX_COMPILER_BASENAME}"
fi
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}nm" ]; then
ln -s "${AVR_NM}" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}nm"
fi
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}objcopy" ]; then
ln -s "${AVR_OBJCOPY}" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}objcopy"
fi
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}ar" ]; then
ln -s "${AVR_AR}" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}ar"
fi
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}size" ]; then
ln -s "${AVR_SIZE}" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}size"
fi
fi
export CCACHE_PATH=${COMPILER_PATH}/
CCACHE_ENABLE="-prefs compiler.path=${CCACHE_WRAPPER_PATH}/"
fi
}
firmware_size () {
if [ "${ARCH}" = "virtual" ]; then
echo "[Size not computed for virtual build]"
return
fi
: "${MAX_PROG_SIZE:=$(get_arduino_pref 'upload.maximum_size')}"
## This is a terrible hack, please don't hurt me. - algernon
set +e
raw_output=$("$@" 2> /dev/null)
rc=$?
set -e
if [ $rc -eq 0 ]; then
output="$(echo "${raw_output}"| grep "\\(Program\\|Data\\):" | sed -e 's,^, - ,' && echo)"
PROGSIZE="$(echo "${output}" | grep "Program:" | cut -d: -f2 | awk '{print $1}')"
PERCENT="$(echo "${PROGSIZE}" "${MAX_PROG_SIZE}" | awk "{ printf \"%02.01f\", \$1 / \$2 * 100 }")"
# we want the sed there, doing with shell builtins would be worse.
# shellcheck disable=SC2001 disable=SC1117
echo "${output}" | sed -e "s/\(Program:.*\)(\([0-9\.]*%\) Full)/\1(${PERCENT}% Full)/"
else
echo "Unable to determine image size."
fi
}
find_sketch () {
if [ -z "${SKETCH}" ]; then
echo "SKETCH needs to be set before including this file!" >&2
exit 1
fi
SKETCH_DIR="${SKETCH}"
SKETCH_BASE_NAME=$(basename "$SKETCH")
SKETCH_FILE_NAME="${SKETCH_BASE_NAME}.ino"
for path in "${SKETCH_DIR}" \
"src" \
"."; do
if [ -f "${path}/${SKETCH_FILE_NAME}" ]; then
SKETCH_DIR="${path}"
SKETCH_FILE_PATH=$(absolute_filename "${SKETCH_DIR}/${SKETCH_FILE_NAME}")
return
fi
done
echo "I couldn't find your sketch (.ino file)" >&2
exit 1
}
prompt_before_flashing () {
flashing_instructions=$(get_arduino_pref 'build.flashing_instructions')
if [ "x${flashing_instructions}x" = "xx" ]; then
flashing_instructions="If your keyboard needs you to do something to put it in flashing mode, do that now."
fi
printf '%b\n\n' "${flashing_instructions}"
echo ""
echo "When you're ready to proceed, press 'Enter'."
# We do not want to permit line continuations here. We just want a newline.
# shellcheck disable=SC2162
read
}
flash () {
compile "$@"
# Check to see if we can see a keyboard bootloader port.
# If we -can-, then we should skip over the "reset to bootloader" thing
find_bootloader_ports
if [ -z "${DEVICE_PORT_BOOTLOADER}" ]; then
prompt_before_flashing
# This is defined in the (optional) user config.
# shellcheck disable=SC2154
${preFlash_HOOKS}
# If we're -not- doing a manual reset, then try to do it automatically
if [ -z "${MANUAL_RESET}" ]; then
reset_device
sleep 2
find_bootloader_ports
# Otherwise, poll for a bootloader port.
else
wait_for_bootloader_port
fi
fi
check_bootloader_port_and_flash
# This is defined in the (optional) user config.
# shellcheck disable=SC2154
${postFlash_HOOKS}
}
wait_for_bootloader_port() {
declare -i tries
tries=15
while [ "$tries" -gt 0 ] && [ -z "${DEVICE_PORT_BOOTLOADER}" ]; do
sleep 1
printf "."
find_bootloader_ports
# the variable annotations do appear to be necessary
# shellcheck disable=SC2004
tries=$(($tries-1))
done
if [ "$tries" -gt 0 ]; then
echo "Found."
else
echo "Timed out."
fi
}
check_bootloader_port () {
if [ -z "${DEVICE_PORT_BOOTLOADER}" ]; then
echo "Unable to detect a keyboard in bootloader mode."
echo "You may need to hold a key or hit a reset button."
echo "Please check your keyboard's documentation"
return 1
fi
}
check_bootloader_port_and_flash () {
if ! check_bootloader_port; then
return 1
fi
echo "Flashing your keyboard:"
# If the flash fails, try a second time
if ! flash_over_usb; then
sleep 2
if ! flash_over_usb; then
if [ "${ARDUINO_VERBOSE}" != "-verbose" ]; then
echo "Something went wrong."
echo "You might want to try flashing again with the VERBOSE environment variable set"
fi
return 1
fi
fi
echo "Keyboard flashed successfully!"
return 0
}
flash_over_usb () {
FLASH_CMD=$(${AVRDUDE} \
-C "${AVRDUDE_CONF}" \
-p"${MCU}" \
-cavr109 \
-D \
-P "${DEVICE_PORT_BOOTLOADER}" \
-b57600 \
"-Uflash:w:${HEX_FILE_PATH}:i")
if [ "${ARDUINO_VERBOSE}" != "-verbose" ]; then
${FLASH_CMD} 2>&1 |grep -v ^avrdude | grep -v '^$' |grep -v '^ ' | grep -vi programmer
return "${PIPESTATUS[0]}"
else
${FLASH_CMD}
return $?
fi
}
flash_from_bootloader() {
compile "$@"
prompt_before_flashing
find_bootloader_ports
check_bootloader_port_and_flash
}
program() {
compile "$@"
prompt_before_flashing
flash_with_programmer
}
flash_with_programmer() {
${AVRDUDE} -v \
-C "${AVRDUDE_CONF}" \
-p"${MCU}" \
-cusbtiny \
-D \
-B 1 \
"-Uflash:w:${HEX_FILE_PATH}:i"
}
find_bootloader_path() {
BOOTLOADER_FILE=$( get_arduino_pref 'bootloader.file' )
: "${BOOTLOADER_FILE:=caterina/Caterina.hex}"
: "${BOOTLOADER_PATH:=${BOARD_HARDWARE_PATH}/keyboardio/avr/bootloaders/${BOOTLOADER_FILE}}"
}
hex_with_bootloader () {
compile
find_bootloader_path
awk '/^:00000001FF/ == 0' "${HEX_FILE_PATH}" > "${HEX_FILE_WITH_BOOTLOADER_PATH}"
echo "Using ${BOOTLOADER_PATH}"
${MD5} "${BOOTLOADER_PATH}"
cat "${BOOTLOADER_PATH}" >> "${HEX_FILE_WITH_BOOTLOADER_PATH}"
ln -sf -- "${OUTPUT_FILE_PREFIX}-with-bootloader.hex" "${OUTPUT_PATH}/${SKETCH_BASE_NAME}-latest-with-bootloader.hex"
cat <<- EOF
Combined firmware and bootloader are now at ${HEX_FILE_WITH_BOOTLOADER_PATH}
Make sure you have the bootloader version you expect.
And TEST THIS ON REAL HARDWARE BEFORE YOU GIVE IT TO ANYONE
EOF
}
build () {
compile "$@"
size "$@"
}
prepare_ccache () {
build_paths
enable_ccache
}
compile () {
find_sketch
build_version
build_paths
build_filenames
# If the hex file is older than the sketch file, or the hex file does not exist
# then rebuild. This is not as correct as letting make check our dependencies
# But it's less broken for most user use cases
# TODO(anyone): Make this suck less
if [ "${HEX_FILE_PATH}" -ot "${SKETCH_FILE_PATH}" ]; then
do_compile "$@"
fi
}
do_compile () {
prepare_ccache
install -d "${OUTPUT_PATH}"
echo "Building ${SKETCH_FILE_PATH}"
# This is defined in the (optional) user config.
# shellcheck disable=SC2154
${compile_HOOKS}
if [ -d "${ARDUINO_LOCAL_LIB_PATH}/libraries" ]; then
# shellcheck disable=SC2089
# We want literal backslashes here, not arrays.
local_LIBS="-libraries \"${ARDUINO_LOCAL_LIB_PATH}/libraries\""
fi
ARDUINO_PACKAGES=""
if [ -d "${ARDUINO_PACKAGE_PATH}" ]; then
# shellcheck disable=SC2089
# We want literal backslashes here, not arrays.
ARDUINO_PACKAGES="-hardware \"${ARDUINO_PACKAGE_PATH}\""
fi
SAVED_BOARD="${BOARD}"
SAVED_FQBN="${FQBN}"
if [ -e "${SKETCH_DIR}/.kaleidoscope-builder.conf" ]; then
# shellcheck disable=SC1090
BOARD="$(. "${SKETCH_DIR}"/.kaleidoscope-builder.conf && echo "${BOARD}")"
# shellcheck disable=SC1090
FQBN="$(. "${SKETCH_DIR}"/.kaleidoscope-builder.conf && echo "${FQBN}")"
if [ -n "${BOARD}" ]; then
if [ -z "${ARCH}" ]; then
FQBN="keyboardio:avr:${BOARD}"
else
FQBN="keyboardio:${ARCH}:${BOARD}"
fi
fi
fi
_CMD_CXX="${CXX:-${COMPILER_PREFIX}${CXX_COMPILER_BASENAME}${COMPILER_SUFFIX}}"
_CMD_CC="${CC:-${COMPILER_PREFIX}${C_COMPILER_BASENAME}${COMPILER_SUFFIX}}"
_CMD_AR="${AR:-${COMPILER_PREFIX}${AR_BASENAME}${COMPILER_SUFFIX}}"
# SC2091: We do not care if quotes or backslashes are not respected.
# SC2086: We want word splitting.
# shellcheck disable=SC2086,SC2090
"${ARDUINO_BUILDER}" \
-compile \
${ARDUINO_PACKAGES} \
-hardware "${ARDUINO_PATH}/hardware" \
-hardware "${BOARD_HARDWARE_PATH}" \
${ARDUINO_TOOLS_FLAG:+"${ARDUINO_TOOLS_FLAG}"} ${ARDUINO_TOOLS_PARAM:+"${ARDUINO_TOOLS_PARAM}"} \
-tools "${ARDUINO_BUILDER_TOOLS_PATH}" \
-fqbn "${FQBN}" \
-libraries "." \
-libraries "${KALEIDOSCOPE_DIR}" \
-libraries "${BOARD_HARDWARE_PATH}/.." \
${local_LIBS} \
${EXTRA_BUILDER_ARGS} \
-build-cache "${CORE_CACHE_PATH}" \
-build-path "${BUILD_PATH}" \
-ide-version "${ARDUINO_IDE_VERSION}" \
-built-in-libraries "${ARDUINO_PATH}/libraries" \
-prefs "compiler.cpp.extra_flags=${ARDUINO_CFLAGS} ${LOCAL_CFLAGS}" \
-prefs "compiler.path=${COMPILER_PATH}" \
-prefs "compiler.c.cmd=${_CMD_CC}" \
-prefs "compiler.cpp.cmd=${_CMD_CXX}" \
-prefs "compiler.ar.cmd=${_CMD_AR}" \
-prefs "compiler.c.elf.cmd=${_CMD_CXX}" \
$CCACHE_ENABLE \
-warnings all \
${ARDUINO_VERBOSE} \
${ARDUINO_AVR_GCC_PREFIX_PARAM} \
"${SKETCH_FILE_PATH}"
if [ -z "${LIBONLY}" ]; then
cp "${BUILD_PATH}/${SKETCH_FILE_NAME}.hex" "${HEX_FILE_PATH}"
cp "${BUILD_PATH}/${SKETCH_FILE_NAME}.elf" "${ELF_FILE_PATH}"
ln -sf "${OUTPUT_FILE_PREFIX}.hex" "${OUTPUT_PATH}/${SKETCH_BASE_NAME}-latest.hex"
ln -sf "${OUTPUT_FILE_PREFIX}.elf" "${OUTPUT_PATH}/${SKETCH_BASE_NAME}-latest.elf"
else
cp "${BUILD_PATH}/${SKETCH_FILE_NAME}.a" "${LIB_FILE_PATH}"
ln -sf "${OUTPUT_FILE_PREFIX}.a" "${OUTPUT_PATH}/${SKETCH_BASE_NAME}-latest.a"
fi
if [ "${ARDUINO_VERBOSE}" = "-verbose" ]; then
echo "Build artifacts can be found in ${BUILD_PATH}";
fi
BOARD="${SAVED_BOARD}"
FQBN="${SAVED_FQBN}"
}
find_all_sketches () {
for plugin in ./*.ino \
$([ -d examples ] && find examples -name '*.ino') \
src/*.ino; do
if [ -d "$(dirname "${plugin}")" ] || [ -f "${plugin}" ]; then
p="$(basename "${plugin}" .ino)"
if [ "${p}" != '*' ]; then
case "${plugin}" in
examples/*/${p}/${p}.ino)
echo "${plugin}" | sed -e "s,examples/,," | sed -e "s,/${p}\\.ino,,"
;;
*)
echo "${p}"
;;
esac
fi
fi
done | sort
}
build_all () {
plugins="$(find_all_sketches)"
for plugin in ${plugins}; do
export SKETCH="${plugin}"
$0 "${plugin}" build
done
}
compile_all () {
plugins="$(find_all_sketches)"
for plugin in ${plugins}; do
export SKETCH="${plugin}"
$0 "${plugin}" compile
done
}
size () {
compile
echo "- Size: ${ELF_FILE_PATH}"
# shellcheck disable=SC2086
firmware_size "${AVR_SIZE}" ${AVR_SIZE_FLAGS} "${ELF_FILE_PATH}"
echo
}
size_map () {
compile
"${AVR_NM}" --size-sort -C -r -l -t decimal "${ELF_FILE_PATH}"
}
disassemble () {
compile
"${AVR_OBJDUMP}" -C -d "${ELF_FILE_PATH}"
}
decompile () {
disassemble
}
clean () {
find_sketch
build_paths
rm -rf -- "${OUTPUT_PATH}"
rm -rf -- "${BUILD_PATH}"
kaleidoscope_dir="$(dirname "$0")/.."
if [ -d "${kaleidoscope_dir}/testing/googletest/build" ]; then
( cd "${kaleidoscope_dir}/testing/googletest/build" &&
cmake .. &&
make clean)
fi
}
reset_device() {
find_device_port
check_device_port
reset_device_cmd
}
check_device_port () {
if [ -z "$DEVICE_PORT" ]; then
cat <<EOF >&2
I couldn't autodetect the keyboard's serial port.
If you see this message and your keyboard is connected to your computer,
it may mean that our serial port detection logic is buggy or incomplete.
In that case, please report this issue at:
https://github.com/keyboardio/Kaleidoscope
EOF
exit 1
elif echo "$DEVICE_PORT" | grep -q '[[:space:]]'; then
cat <<EOF >&2
Unexpected whitespace found in detected serial port:
$DEVICE_PORT
If you see this message, it means that our serial port
detection logic is buggy or incomplete.
Please report this issue at:
https://github.com/keyboardio/Kaleidoscope
EOF
exit 1
fi
if ! [ -w "$DEVICE_PORT" ]; then
cat <<EOF >&2
In order to update your keyboard's firmware you need to have permission
to write to its serial port $DEVICE_PORT.
It appears that you do not have this permission:
$(ls -l "$DEVICE_PORT")
This may be because you're not in the correct unix group:
$(stat -c %G "$DEVICE_PORT").
You are currently in the following groups:
$(id -Gn)
Please ensure you have followed the instructions on setting up your
account to be in the right group:
https://github.com/keyboardio/Kaleidoscope/wiki/Install-Arduino-support-on-Linux
EOF
exit 1
fi
}
usage () {
cat <<- EOF
Usage: $0 SKETCH commands...
Runs all of the commands in the context of the Sketch.
Available commands:
help
This help screen.
compile
Compiles the sketch.
size
Reports the size of the compiled sketch.
build
Runs compile and report-size.
clean
Cleans up the output directory.
size-map
Displays the size map for the sketch.
disassemble
Decompile the sketch.
reset-device
Reset the device.
flash
Flashes the firmware using avrdude.
build-all
Build all Sketches we can find.
EOF
}
help () {
usage
}
if [ $# -lt 1 ]; then
usage
exit 1
fi
## Parse the command-line
## - anything that has a =, is an env var
## - from the remaining stuff, the first one is the Library/Sketch
## - everything else are commands
##
## - if there is only one argument, that's a command
KALEIDOSCOPE_DIR="$(cd "$(dirname "$0")"/..; pwd)"
# shellcheck disable=SC2034
KALEIDOSCOPE_BIN_DIR="${KALEIDOSCOPE_DIR}/bin/"
# shellcheck disable=SC2155
export SOURCEDIR="$(pwd)"
for conf_file in \
"${HOME}/.kaleidoscope-builder.conf" \
"${SOURCEDIR}/.kaleidoscope-builder.conf" \
"${SOURCEDIR}/kaleidoscope-builder.conf" \
"${KALEIDOSCOPE_DIR}/etc/kaleidoscope-builder.conf"; do
if [ -e "${conf_file}" ]; then
# shellcheck disable=SC1090
. "${conf_file}"
fi
done
# shellcheck disable=SC1090
if [ -n "${VERBOSE}" ] && [[ "${VERBOSE}" -gt 0 ]]; then
ARDUINO_VERBOSE="-verbose"
else
ARDUINO_VERBOSE="-quiet"
fi
cmds=""
## Export vars
for i in $(seq 1 $#); do
v="$1"
shift
case "${v}" in
*=*)
# Exporting an expansion is *precisely* what we want here.
# shellcheck disable=SC2086,SC2163
export ${v}
;;
*)
cmds="${cmds} ${v}"
;;
esac
done
# Word splitting is desired here.
# shellcheck disable=SC2086
set -- ${cmds}
if [ $# -eq 1 ]; then
cmd="$(echo "$1" | tr '-' '_')"
${cmd}
exit $?
fi
SKETCH="$1"
shift
if [ "${SKETCH}" = "default" ]; then
SKETCH="${DEFAULT_SKETCH}"
fi
cmds=""
# shellcheck disable=2034
for i in $(seq 1 $#); do
cmds="${cmds} $(echo "$1" | tr '-' '_')"
shift
done
for cmd in ${cmds}; do
${cmd}
done

@ -13,64 +13,36 @@ fi
if [ "${uname_S}" = "Darwin" ]; then if [ "${uname_S}" = "Darwin" ]; then
ARDUINO_LOCAL_LIB_PATH="${ARDUINO_LOCAL_LIB_PATH:-${HOME}/Documents/Arduino}"
# This stops macos from copying resource forks into thigns like tar # This stops macos from copying resource forks into thigns like tar
export COPYFILE_DISABLE=true export COPYFILE_DISABLE=true
else else
ARDUINO_LOCAL_LIB_PATH="${ARDUINO_LOCAL_LIB_PATH:-${HOME}/Arduino}" ARDUINO_LOCAL_LIB_PATH="${ARDUINO_LOCAL_LIB_PATH:-${HOME}/Arduino}"
fi fi
BOARD_HARDWARE_PATH="${BOARD_HARDWARE_PATH:-${ARDUINO_LOCAL_LIB_PATH}/hardware}"
if [ ! -d ./.arduino/user/hardware ]; then echo "Preparing Kaleidoscope and the bundle..."
echo "Running tests in Docker requires that your Arduino environment be installed in .arduino inside the top-level Kaleidoscope directory. To set this up, run 'make setup'"
exit 1
fi
XFER_DIR="$(pwd)/.docker_xfer" XFER_DIR="$(pwd)/.docker_xfer"
mkdir -p "${XFER_DIR}" mkdir -p "${XFER_DIR}"
if [ -z "$_NO_SYNC_KALEIDOSCOPE" ]; then
echo "Preparing Kaleidoscope..."
echo "The bundle is coming from ${ARDUINO_DIRECTORIES_USER}/hardware/keyboardio"
tar -cf "${XFER_DIR}/kaleidoscope.tar" \ tar -cf "${XFER_DIR}/kaleidoscope.tar" \
--exclude .arduino/data \ --exclude .docker_xfer --exclude .git --exclude _build --exclude testing/googletest/build \
--exclude .arduino/downloads \
--exclude .arduino/user/hardware/keyboardio/avr/libraries/Kaleidoscope \
--exclude .arduino/user/hardware/keyboardio/gd32/libraries/Kaleidoscope \
--exclude bin/arduino-cli \
--exclude .docker_xfer \
--exclude .git \
--exclude _build \
--exclude testing/googletest/build \
. .
fi (cd "${BOARD_HARDWARE_PATH}/keyboardio" && tar -cf "${XFER_DIR}/bundle.tar" \
--exclude .git --exclude avr/libraries/Kaleidoscope .)
if [ -z "$DOCKER_LIVE_KALEIDOSCOPE_DIR" ]; then
_KALEIDOSCOPE_MOUNT="--mount type=tmpfs,destination=/kaleidoscope:exec"
else
echo "Kaleidoscope is mounted read/write inside docker"
_KALEIDOSCOPE_MOUNT="-v $(pwd):/kaleidoscope:"
fi
echo "Building the docker image. This could take a few minutes." echo "Building the docker image..."
docker build -q -t kaleidoscope/docker etc docker build -q -t kaleidoscope/docker etc
# We do want word splitting since there are multiple options here # shellcheck disable=SC2086 # We do want word splitting since there are multiple options here
# shellcheck disable=SC2086
docker run --rm $DOCKER_RUN_INTERACTIVE_OPTS \ docker run --rm $DOCKER_RUN_INTERACTIVE_OPTS \
${_KALEIDOSCOPE_MOUNT} \ --tmpfs /kaleidoscope:exec \
--mount type=bind,source="${XFER_DIR}",destination=/kaleidoscope-src,consistency=delegated,readonly \ --mount type=bind,source="${XFER_DIR}",destination=/kaleidoscope-src,consistency=delegated,readonly \
--mount type=volume,source=kaleidoscope-persist,destination=/kaleidoscope-persist,consistency=delegated \ --mount type=volume,source=kaleidoscope-persist,destination=/kaleidoscope-persist,consistency=delegated \
--mount type=volume,source=kaleidoscope-googletest-build,destination=/kaleidoscope/testing/googletest/build,consistency=delegated \ --mount type=volume,source=kaleidoscope-googletest-build,destination=/kaleidoscope/hardware/keyboardio/avr/libraries/Kaleidoscope/testing/googletest/build,consistency=delegated \
--mount type=volume,source=kaleidoscope-build,destination=/kaleidoscope/_build,consistency=delegated \ --mount type=volume,source=kaleidoscope-build,destination=/kaleidoscope/hardware/keyboardio/avr/libraries/Kaleidoscope/_build,consistency=delegated \
--env ARDUINO_DIRECTORIES_DATA=/arduino-cli/data \
--env ARDUINO_DIRECTORIES_USER=/kaleidoscope/.arduino/user/ \
--env _NO_SYNC_KALEIDOSCOPE=${_NO_SYNC_KALEIDOSCOPE} \
kaleidoscope/docker "$*" kaleidoscope/docker "$*"

@ -0,0 +1,15 @@
#!/bin/bash -e
# This script sets all of the files inside src and example to have mtimes
# that match the times of the last git commit that touched each file
# This can be useful when build tools depend on file timestamps to
# make caching decisions
find src examples -type f -exec sh -c '
timestamp=$(git log --pretty=format:%ad --date=format:%Y%m%d%H%M.%S -n 1 HEAD "$1" 2> /dev/null)
if [ "x$timestamp" != "x" ]; then
touch -t "$timestamp" "$1"
fi
' sh {} \;

1
docs/.gitignore vendored

@ -1 +0,0 @@
examples/

@ -12,27 +12,13 @@ BUILDDIR = ../out/docs
help: help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile clean examples .PHONY: help Makefile clean
clean: clean:
rm -rf doxyoutput/ api/ rm -rf doxyoutput/ api/
@$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) @$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
copy-plugin-readmes:
install -d plugins
(cd ../plugins; \
ls -d * |xargs -n 1 -I % cp %/README.md ../docs/plugins/%.md )
examples:
install -d examples
(cd ../examples; \
find . -type f \( -name \*ino -or -name \*h -or -name \*cpp \) \
) | \
xargs -n 1 -I % ../bin/docs/example-to-doc %
# Catch-all target: route all unknown targets to Sphinx using the new # Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile %: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

@ -12,120 +12,6 @@ See [UPGRADING.md](UPGRADING.md) for more detailed instructions about upgrading
## New features ## New features
### OneShot public functions
The OneShot plugin now allows other plugins to control the OneShot state of
individual keys, by calling one of the following:
- `OneShot.setPending(key_addr)`: Put the key at `key_addr` in the "pending"
OneShot state. This will make that key act like any other OneShot key until
it is cancelled by a subsequent keypress. Once a key is in this state,
OneShot will manage it from that point on, including making the key "sticky"
if it is double-tapped.
- `OneShot.setSticky(key_addr)`: Put the key at `key_addr` in the "sticky"
OneShot state. The key will be released by OneShot when it is tapped again.
- `OneShot.setOneShot(key_addr)`: Put the key at `key_addr` in the "one-shot"
state. This is normally the state OneShot key will be in after it has been
tapped. Calling `setPending()` is more likely to be useful.
- `OneShot.clear(key_addr)`: Clear the OneShot state of the key at `key_addr`.
Note: Any plugin that calls one of these OneShot methods must either be
registered in `KALEIDOSCOPE_INIT_PLUGINS()` after OneShot, or it must add the
`INJECTED` bit to the keyswitch state of the event (i.e. `event.state |=
INJECTED`) to prevent OneShot from prematurely advancing keys to the next
OneShot state.
### SpaceCadet "no-delay" mode
SpaceCadet can now be enabled in "no-delay" mode, wherein the primary (modifier)
value of the key will be sent to the host immediately when the key is pressed.
If the SpaceCadet key is released before the timeout, the modifier is released,
and then the alternate (symbol) value is sent. To activate "no-delay" mode, call `SpaceCadet.enableWithoutDelay()`.
### New Qukeys features
#### Tap-repeat
It is now possible to get the "tap" value of a qukey to repeat (as if that key
for that character was simply being held down on a normal keyboard) by tapping
the qukey, then quickly pressing and holding it down. The result on the OS will
be as if the key was pressed and held just once, so that users of macOS apps
that use the Cocoa input system can get the menu for characters with diacritics
without an extra character in the output.
The maximum interval between the two keypresses that will trigger a tap repeat
can be configured via the `Qukeys.setMaxIntervalForTapRepeat(ms)` function,
where the argument specifies the number of milliseconds Qukeys will wait after a
qukey is tapped for it to be pressed a second time. If it is, but the qukey is
released within that same interval from the first tap's release, it will be
treated as a double-tap, and both taps will be sent to the OS.
### New OneShot features
#### Auto-OneShot modifiers & layers
OneShot can now treat modifiers and layer-shift keys as automatic OneShot
keys. This includes modifiers with other modifier flags applied, so it is now
very simple to turn `Key_Meh` or `Key_Hyper` into a OneShot key. The feature is
controlled by the following new functions:
- `OneShot.toggleAutoModifiers()`: Turn auto-OneShot modifiers on or off.
- `OneShot.toggleAutoLayers()`: Turn auto-OneShot layer shifts on or off.
- `OneShot.toggleAutoOneShot()`: Both of the above.
There are also `enable` and `disable` versions of these functions.
Note, it is still possible to define a modifier key in the keymap that will not
automatically become a OneShot key when pressed, by applying modifier flags to
`Key_NoKey` (e.g. `LSHIFT(Key_NoKey)`).
#### Two new special OneShot keys
OneShot can now also turn _any_ key into a sticky key, using either of two
special `Key` values that can be inserted in the keymap.
##### `OneShot_MetaStickyKey`
This is a special OneShot key (it behaves like other OneShot keys), but its
effect is to make any key pressed while it is active sticky. Press
`OneShot_MetaStickyKey`, then press `X`, and `X` will become sticky. Sticky
keys can be deactivated just like other OneShot keys, by pressing them
again. This works for any key value, so use it with caution.
##### `OneShot_ActiveStickyKey`
Like `OneShot_ActiveStickyKey`, this key makes other keys sticky, but rather than
affecting a subsequent key, it affects any keys already held when it is
pressed. Press `X`, press `OneShot_ActiveStickyKey`, and release `X`, and `X`
will be sticky until it is pressed again to deactivate it. Again, it works on
any key value, so use with caution.
#### LED-ActiveModColor highlighting
With the updates to OneShot, LED-ActiveModColor now recognizes and highlights
OneShot keys in three different states (along with normal modifiers):
- one-shot (a key that's active after release, but will time out)
- sticky (a key that will stay active indefinitely after release)
- normal (a key that will stay active only while physically held; also applies
to normal modifier keys)
The colors of theses three highlights are controlled by the properties
`ActiveModColorEffect.oneshot_color`, `ActiveModColorEffect.sticky_color`, and
`ActiveModColorEffect.highlight_color`, respectively.
### Better protection against unintended modifiers from Qukeys
Qukeys has two new configuration options for preventing unintended modifiers in
the output, particularly when typing fast:
- `Qukeys.setMinimumHoldTime(ms)` sets the minimum duration of a qukey press
required for it to be eligible to take on its alternate (modifier) value.
- `Qukeys.setMinimumPriorInterval(ms)` sets the minimum interval between the
previous printable (letters, numbers, and punctuation) key press and the press
of the qukey required to make the qukey eligible to take on its alternate
(modifier) value.
### KALEIDOSCOPE_API_VERSION bump ### KALEIDOSCOPE_API_VERSION bump
`KALEIDOSCOPE_API_VERSION` has been bumped to **2** due to the plugin API `KALEIDOSCOPE_API_VERSION` has been bumped to **2** due to the plugin API
@ -188,23 +74,23 @@ See the [Kaleidoscope-USB-Quirks][plugin:USB-Quirks] plugin for a use-case.
### Finer stickability controls for OneShot ### Finer stickability controls for OneShot
The [OneShot plugin](plugins/Kaleidoscope-OneShot.md) gained finer stickability controls, one can now control whether the double-tap stickiness is enabled on a per-key basis. See [UPGRADING.md](UPGRADING.md#finer-oneshot-stickability-control) for more information. The [OneShot plugin](plugins/OneShot.md) gained finer stickability controls, one can now control whether the double-tap stickiness is enabled on a per-key basis. See [UPGRADING.md](UPGRADING.md#finer-oneshot-stickability-control) for more information.
### A way to slow down Unicode input ### A way to slow down Unicode input
In certain cases we need to delay the unicode input sequence, otherwise the host is unable to process the input properly. For this reason, the [Unicode](plugins/Kaleidoscope-Unicode.md) gained an `.input_delay()` method that lets us do just that. It still defaults to no delay. In certain cases we need to delay the unicode input sequence, otherwise the host is unable to process the input properly. For this reason, the [Unicode](plugins/Unicode.md) gained an `.input_delay()` method that lets us do just that. It still defaults to no delay.
### Better support for modifiers in the Cycle plugin ### Better support for modifiers in the Cycle plugin
The [Cycle](plugins/Kaleidoscope-Cycle.md) plugin has much better support for cycling through keys with modifiers applied to them, such as `LSHIFT(Key_A)`. Please see the documentation and the updated example for more information. The [Cycle](plugins/Cycle.md) plugin has much better support for cycling through keys with modifiers applied to them, such as `LSHIFT(Key_A)`. Please see the documentation and the updated example for more information.
### More control over when to send reports during Macro playback ### More control over when to send reports during Macro playback
There are situations where one would like to disable sending a report after each and every step of a macro, and rather have direct control over when reports are sent. The new `WITH_EXPLICIT_REPORT`, `WITH_IMPLICIT_REPORT` and `SEND_REPORT` steps help with that. Please see the [Macros](plugins/Kaleidoscope-Macros.md) documentation for more information. There are situations where one would like to disable sending a report after each and every step of a macro, and rather have direct control over when reports are sent. The new `WITH_EXPLICIT_REPORT`, `WITH_IMPLICIT_REPORT` and `SEND_REPORT` steps help with that. Please see the [Macros](plugins/Macros.md) documentation for more information.
### LED-ActiveModColor can be asked to not highlight normal modifiers ### LED-ActiveModColor can be asked to not highlight normal modifiers
The plugin was intended to work with OneShot primarily, and that's where it is most useful. To make it less surprising, and more suitable to include it in default-like firmware, we made it possible to ask it not to highlight normal modifiers. Please see the [LED-ActiveModColor](plugins/Kaleidoscope-LED-ActiveModColor.md) documentation for more information. The plugin was intended to work with OneShot primarily, and that's where it is most useful. To make it less surprising, and more suitable to include it in default-like firmware, we made it possible to ask it not to highlight normal modifiers. Please see the [LED-ActiveModColor](plugins/LED-ActiveModColor.md) documentation for more information.
### Events now trigger on layer changes ### Events now trigger on layer changes
@ -234,47 +120,39 @@ Kaleidoscope has been ported to the following devices:
For more information, please see the hardware plugins' documentation. For more information, please see the hardware plugins' documentation.
To make it easier to port Kaleidoscope, we introduced the `ATMegaKeyboard` base class. For any board that's based on the ATMega MCU and a simple matrix, this might be a good foundation to develop the hardware plugin upon. To make it easier to port Kaleidoscope, we introduced the [ATMegaKeyboard](plugins/ATMegaKeyboard.md) base class. For any board that's based on the ATMega MCU and a simple matrix, this might be a good foundation to develop the hardware plugin upon.
## New plugins ## New plugins
### CharShift
The [CharShift](plugins/Kaleidoscope-CharShift.md) plugin allows independent assignment of symbols to keys depending on whether or not a `shift` key is held.
### AutoShift
The [AutoShift](plugins/Kaleidoscope-AutoShift.md) plugin provides an alternative way to get shifted symbols, by long-pressing keys instead of using a separate `shift` key.
### DynamicMacros ### DynamicMacros
The [DynamicMacros](plugins/Kaleidoscope-DynamicMacros.md) plugin provides a way to use and update macros via the Focus API, through Chrysalis. The [DynamicMacros](plugins/DynamicMacros.md) plugin provides a way to use and update macros via the Focus API, through Chrysalis.
### IdleLEDs ### IdleLEDs
The [IdleLEDs](plugins/Kaleidoscope-IdleLEDs.md) plugin is a simple, yet, useful one: it will turn the keyboard LEDs off after a period of inactivity, and back on upon the next key event. The [IdleLEDs](plugins/IdleLEDs.md) plugin is a simple, yet, useful one: it will turn the keyboard LEDs off after a period of inactivity, and back on upon the next key event.
### LEDActiveLayerColor ### LEDActiveLayerColor
The [LEDActiveLayerColor][plugins/Kaleidoscope-LEDActiveLayerColor.md] plugin makes it possible to set the color of all LEDs to the same color, depending on which layer is active topmost. The [LEDActiveLayerColor][plugins/LEDActiveLayerColor.md] plugin makes it possible to set the color of all LEDs to the same color, depending on which layer is active topmost.
### LED-Wavepool ### LED-Wavepool
We integrated the [LEDWavepool](plugins/Kaleidoscope-LED-Wavepool.md) plugin by [ToyKeeper][wavepool:origin], with a few updates and new features added. We integrated the [LEDWavepool](plugins/LED-Wavepool.md) plugin by [ToyKeeper][wavepool:origin], with a few updates and new features added.
[wavepool:origin]: https://github.com/ToyKeeper/Kaleidoscope-LED-Wavepool [wavepool:origin]: https://github.com/ToyKeeper/Kaleidoscope-LED-Wavepool
### Turbo ### Turbo
The [Turbo](plugins/Kaleidoscope-Turbo.md) plugin provides a way to send keystrokes in very quick succession while holding down a key. The [Turbo](plugins/Turbo.md) plugin provides a way to send keystrokes in very quick succession while holding down a key.
### WinKeyToggle ### WinKeyToggle
The [WinKeyToggle](plugins/Kaleidoscope-WinKeyToggle.md) plugin assists with toggling the Windows key on and off - a little something for those of us who game under Windows and are tired of accidentally popping up the start menu. The [WinKeyToggle](plugins/WinKeyToggle.md) plugin assists with toggling the Windows key on and off - a little something for those of us who game under Windows and are tired of accidentally popping up the start menu.
### FirmwareDump ### FirmwareDump
The [FirmwareDump](plugins/Kaleidoscope-FirmwareDump.md) plugin makes it possible to dump one's firmware over Focus. The [FirmwareDump](plugins/FirmwareDump.md) plugin makes it possible to dump one's firmware over Focus.
## Breaking changes ## Breaking changes
@ -307,29 +185,29 @@ The `NumPad` plugin used to toggle `NumLock` when switching to the NumPad layer.
### The `RxCy` macros and peeking into the keyswitch state ### The `RxCy` macros and peeking into the keyswitch state
The `RxCy` macros changed from being indexes into a per-hand bitmap to being an index across the whole keyboard. This mostly affected the [MagicCombo](plugins/Kaleidoscope-MagicCombo.md) plugin. The `RxCy` macros changed from being indexes into a per-hand bitmap to being an index across the whole keyboard. This mostly affected the [MagicCombo](plugins/MagicCombo.md) plugin.
Please see the [relevant upgrade notes](UPGRADING.md#the-rxcy-macros-and-peeking-into-the-keyswitch-state) for more information. Please see the [relevant upgrade notes](UPGRADING.md#the-rxcy-macros-and-peeking-into-the-keyswitch-state) for more information.
### The `Redial` plugin had a breaking API change ### The `Redial` plugin had a breaking API change
The [Redial](plugins/Kaleidoscope-Redial.md) plugin was simplified, one no longer needs to define `Key_Redial` on their own, the plugin defines it itself. See the [upgrade notes](UPGRADING.md#Redial) for more information about how to upgrade. The [Redial](plugins/Redial.md) plugin was simplified, one no longer needs to define `Key_Redial` on their own, the plugin defines it itself. See the [upgrade notes](UPGRADING.md#Redial) for more information about how to upgrade.
### Color palette storage has changed ### Color palette storage has changed
The [LED-Palette-Theme](plugins/Kaleidoscope-LED-Palette-Theme.md) had to be changed to store the palette colors in reverse. This change had to be made in order to not default to a bright white palette, that would draw so much power that most operating systems would disconnect the keyboard due to excessive power usage. With inverting the colors, we now default to a black palette instead. This sadly breaks existing palettes, and you will have to re-set the colors. The [LED-Palette-Theme](plugins/LED-Palette-Theme.md) had to be changed to store the palette colors in reverse. This change had to be made in order to not default to a bright white palette, that would draw so much power that most operating systems would disconnect the keyboard due to excessive power usage. With inverting the colors, we now default to a black palette instead. This sadly breaks existing palettes, and you will have to re-set the colors.
We also changed when we reserve space for the palette in EEPROM: we used to do it as soon as possible, but that made it impossible to go from a firmware that does not use the plugin to one that does, and still have a compatible EEPROM layout. We now reserve space as late as possible. This breaks existing EEPROM layouts however. We also changed when we reserve space for the palette in EEPROM: we used to do it as soon as possible, but that made it impossible to go from a firmware that does not use the plugin to one that does, and still have a compatible EEPROM layout. We now reserve space as late as possible. This breaks existing EEPROM layouts however.
### EEPROM-Keymap changed Focus commands ### EEPROM-Keymap changed Focus commands
The [EEPROMKeymap](plugins/Kaleidoscope-EEPROM-Keymap.md) plugin was changed to treat built-in (default) and EEPROM-stored (custom) layers separately, because that's less surprising, and easier to work with from Chrysalis. The old `keymap.map` and `keymap.roLayers` commands are gone, the new `keymap.default` and `keymap.custom` commands should be used instead. The [EEPROMKeymap](plugins/EEPROM-Keymap.md) plugin was changed to treat built-in (default) and EEPROM-stored (custom) layers separately, because that's less surprising, and easier to work with from Chrysalis. The old `keymap.map` and `keymap.roLayers` commands are gone, the new `keymap.default` and `keymap.custom` commands should be used instead.
### EEPROMSettings' version() setter has been deprecated ### EEPROMSettings' version() setter has been deprecated
We're repurposing the `version` setting: instead of it being something end-users We're repurposing the `version` setting: instead of it being something end-users
can set, we'll be using it internally to track changes made to can set, we'll be using it internally to track changes made to
[EEPROMSettings](plugins/Kaleidoscope-EEPROM-Settings.md) itself, with the goal of [EEPROMSettings](plugins/EEPROM-Settings.md) itself, with the goal of
allowing external tools to aid in migrations. The setting wasn't widely used - allowing external tools to aid in migrations. The setting wasn't widely used -
if at all -, which is why we chose to repurpose it instead of adding a new if at all -, which is why we chose to repurpose it instead of adding a new
field. field.

@ -7,24 +7,15 @@ If any of this does not make sense to you, or you have trouble updating your .in
* [Upgrade notes](#upgrade-notes) * [Upgrade notes](#upgrade-notes)
+ [New features](#new-features) + [New features](#new-features)
- [New event handler](#new-event-handler)
- [Event-driven main loop](#event-driven-main-loop)
- [Keyboard state array](#keyboard-state-array)
- [New build system](#new-build-system)
- [New device API](#new-device-api) - [New device API](#new-device-api)
- [New plugin API](#new-plugin-api) - [New plugin API](#new-plugin-api)
- [Bidirectional communication for plugins](#bidirectional-communication-for-plugins) - [Bidirectional communication for plugins](#bidirectional-communication-for-plugins)
- [Consistent timing](#consistent-timing) - [Consistent timing](#consistent-timing)
+ [Breaking changes](#breaking-changes) + [Breaking changes](#breaking-changes)
- [Macros](#macros)
- [Removed `kaleidoscope-builder`](#removed-kaleidoscope-builder)
- [OneShot meta keys](#oneshot-meta-keys)
- [git checkouts aren't compatible with Arduino IDE (GUI)]([#repository-rearchitecture)
- [Layer system switched to activation-order](#layer-system-switched-to-activation-order) - [Layer system switched to activation-order](#layer-system-switched-to-activation-order)
- [The `RxCy` macros and peeking into the keyswitch state](#the-rxcy-macros-and-peeking-into-the-keyswitch-state) - [The `RxCy` macros and peeking into the keyswitch state](#the-rxcy-macros-and-peeking-into-the-keyswitch-state)
- [HostOS](#hostos) - [HostOS](#hostos)
- [MagicCombo](#magiccombo) - [MagicCombo](#magiccombo)
- [OneShot](#oneshot)
- [Qukeys](#qukeys) - [Qukeys](#qukeys)
- [TypingBreaks](#typingbreaks) - [TypingBreaks](#typingbreaks)
- [Redial](#redial) - [Redial](#redial)
@ -41,111 +32,6 @@ any API we've included in a release. Typically, this means that any code that us
## New features ## New features
### New event handler
One more `KeyEvent` handler has been added: `afterReportingState(const KeyEvent &event)`. This handler gets called after HID reports are sent for an event, providing a point for plugins to act after an event has been fully processed by `Runtime.handleKeyEvent()`.
### Event-driven main loop
Kaleidoscope's main loop has been rewritten. It now responds to key toggle-on and toggle-off events, dealing with one event at a time (and possibly more than one in a given cycle). Instead of sending a keyboard HID report at the end of every scan cycle (and letting the HID module suppress duplicates), it now only sends HID reports in response to input events.
Furthermore, there are now two functions for initiating the processing of key events:
- `Runtime.handleKeyswitchEvent()` is the starting point for events that represent physical keyswitches toggling on or off.
- `Runtime.handleKeyEvent()` is the starting point for "artificial" key events. It is also called at the end of `handleKeyswitchEvent()`.
In general, if a plugin needs to generate a key event, it should call `handleKeyEvent()`, not `handleKeyswitchEvent()`.
Each of the above functions calls its own set of plugin event handlers. When those event handlers are all done, event processing continues as `handleKeyEvent()` prepares a new keyboard HID report, then sends it:
- `Runtime.prepareKeyboardReport()` first clears the HID report, then populates it based on the contents of the `live_keys[]` array. Note that the HID report is not cleared until _after_ the new plugin event handlers have been called.
- `Runtime.sendKeyboardReport()` handles generating extra HID reports required for keys with keyboard modifier flags to avoid certain bugs, then calls a new plugin event handler before finally sending the new HID report.
These functions should rarely, if ever, need to be called by plugins.
#### The `KeyEvent` data type
There is a new `KeyEvent` type that encapsulates all the data relevant to a new key event, and it is used as the parameter for the new event-handling functions.
- `event.addr` contains the `KeyAddr` associated with the event.
- `event.state` contains the state bitfield (`uint8_t`), which can be tested with `keyToggledOn()`/`keyToggledOff()`.
- `event.key` contains a `Key` value, usually looked up from the keymap.
- `event.id` contains a pseudo-unique ID number of type `KeyEventId` (an 8-bit integer), used by certain plugins (see `onKeyswitchEvent()` below).
#### New plugin event handlers
##### `onKeyswitchEvent(KeyEvent &event)`
##### `onKeyEvent(KeyEvent &event)`
##### `onAddToReport(Key key)`
##### `beforeReportingState(const KeyEvent &event)`
#### For end-users
Existing sketches should be mostly backwards-compatible, but some updates will be needed for sketches that use custom code. In particular, users of the Macros plugin are likely to need to make adjustments to the code in the user-defined `macroAction()` function, including that function's signature, the new version of which takes a `KeyEvent` parameter instead of just an event state value. In most cases, this will make the resulting code more straightforward without any loss of functionality.
In addition to Macros, these changes might also affect user-defined code executed by the TapDance, Leader, and Syster plugins. Please see the documentation and examples for the affected plugins for details.
### Keyboard State array
The keymap cache (`Layer_::live_composite_keymap_[]`) has been replaced by a keyboard state array (`kaleidoscope::live_keys[]`). The top-level functions that handle keyswitch events have been updated to treat this new array as a representation of the current state of the keyboard, with corresponding `Key` values for any keys that are active (physically held or activated by a plugin).
#### For end-users
There should be no user-visible changes for anyone who simply uses core plugins. A few functions have been deprecated (`Layer.eventHandler()` & `Layer.updateLiveCompositeKeymap()`), but there are straightforward replacements for both.
#### For developers
The major changes are to the `handleKeyswitchEvent()` function, which has been reorganized in order to update the new keyboard state array with correct values at the appropriate times. In addition to that, two new facilities are available:
##### `EventHandlerResult::ABORT`
This is a new return value available to plugin event handlers, which is similar to `EVENT_CONSUMED` in that it causes the calling hook function to return early (stopping any subsequent handlers from seeing the event), but is treated differently by `handleKeyswitchEvent()`. If a handler returns `EVENT_CONSUMED`, the keyboard state array will still be updated by `handleKeyswitchEvent()`, but if it returns `ABORT`, it will not. In both cases, no further event processing will be done by the built-in event handler.
##### `live_keys[key_addr]`
This is the new facility for checking the value of an entry in the keyboard state array. It is indexed directly by `KeyAddr` values, without the need to convert them to integers first. For example, it could be used in a range-based `for` loop to check for values of interest:
```c++
for (KeyAddr key_addr : KeyAddr::all()) {
Key key = live_keys[key_addr];
if (key == Key_LeftShift || key == Key_RightShift) {
// do something special...
}
}
```
Additionally, if the `KeyAddr` values are not needed, one can use the iterator from the new `KeyMap` class like so:
```c++
for (Key key : live_keys.all()) {
if (key == Key_X) {
// do something special...
}
}
```
The `live_keys` object's subscript operator can also be used to set values in the keyboard state array:
```c++
live_keys[key_addr] = Key_X;
```
It also comes with several convenience functions which can be used to make the intention of the code clear:
```c++
// Set a value in the keyboard state array to a specified Key value:
live_keys.activate(key_addr, Key_X);
// Set a value to Key_Inactive, deactivating the key:
live_keys.clear(key_addr);
// Set all values in the array to Key_Inactive:
live_keys.clear();)
// Set a value to Key_Masked, masking the key until its next release event:
live_keys.mask(key_addr);
```
In most cases, it won't be necessary for plugins or user sketches to call any of these functions directly, as the built-in event handler functions will manage the keyboard state array automatically.
### New build system
In this release, we replace kaleidoscope-builder with a new Makefile based build system that uses `arduino-cli` instead of of the full Arduino IDE. This means that you can now check out development copies of Kaliedoscope into any directory, using the `KALEIDOSCOPE_DIR` environment variable to point to your installation.
### New device API ### New device API
We are introducing - or rather, replacing - the older hardware plugins, with a system that's much more composable, more extensible, and will allow us to better support new devices, different MCUs, and so on. We are introducing - or rather, replacing - the older hardware plugins, with a system that's much more composable, more extensible, and will allow us to better support new devices, different MCUs, and so on.
@ -156,7 +42,7 @@ For end users, this doesn't come with any breaking changes. A few things have be
#### For developers #### For developers
For those wishing to port Kaleidoscope to devices it doesn't support yet, the new API should make most things considerably easier. Please see the documentation in [device-apis.md](api-reference/device-apis.md). For those wishing to port Kaleidoscope to devices it doesn't support yet, the new API should make most things considerably easier. Please see the (work in progress) documentation in [doc/device-apis.md](doc/device-apis.md).
The old symbols and APIs are no longer available. The old symbols and APIs are no longer available.
@ -209,7 +95,6 @@ In practice, this boils down to implementing one or more of the following hook p
- `beforeEachCycle()`: Called once, at the beginning of each cycle of the main loop. This is similar to the old "loop hook" with its `post_clear` argument set to false. Takes no arguments, must return `kaleidoscope::EventHandlerResult::OK`. - `beforeEachCycle()`: Called once, at the beginning of each cycle of the main loop. This is similar to the old "loop hook" with its `post_clear` argument set to false. Takes no arguments, must return `kaleidoscope::EventHandlerResult::OK`.
- `onKeyswitchEvent`: Called for every non-idle key event. This replaces the old "event handler hook". It takes a key reference, a key address, and a key state. The key reference can be updated to change the key being processed, so that any plugin that processes it further, will see the updated key. Can return `kaleidoscope::EventHandlerResult::OK` to let other plugins process the event further, or `kaleidoscope::EventHandlerResult::EVENT_CONSUMED` to stop processing. - `onKeyswitchEvent`: Called for every non-idle key event. This replaces the old "event handler hook". It takes a key reference, a key address, and a key state. The key reference can be updated to change the key being processed, so that any plugin that processes it further, will see the updated key. Can return `kaleidoscope::EventHandlerResult::OK` to let other plugins process the event further, or `kaleidoscope::EventHandlerResult::EVENT_CONSUMED` to stop processing.
- `onFocusEvent`: Used to implement [bi-directional communication](#bidirectional-communication-for-plugins). This is called whenever the firmware receives a command from the host. The only argument is the command name. Can return `kaleidoscope::EventHandlerResult::OK` to let other plugins process the event further, or `kaleidoscope::EventHandlerResult::EVENT_CONSUMED` to stop processing. - `onFocusEvent`: Used to implement [bi-directional communication](#bidirectional-communication-for-plugins). This is called whenever the firmware receives a command from the host. The only argument is the command name. Can return `kaleidoscope::EventHandlerResult::OK` to let other plugins process the event further, or `kaleidoscope::EventHandlerResult::EVENT_CONSUMED` to stop processing.
- `onNameQuery`: Used by the [Focus](#bidirecional-communication-for-plugins) plugin, when replying to a `plugins` command. Should either send the plugin name, or not be implemented at all, if the host knowing about the plugin isn't important.
- `beforeReportingState`: Called without arguments, just before sending the keyboard and mouse reports to the host. Must return `kaleidoscope::EventHandlerResult::OK`. - `beforeReportingState`: Called without arguments, just before sending the keyboard and mouse reports to the host. Must return `kaleidoscope::EventHandlerResult::OK`.
- `afterEachCycle`: Called without arguments at the very end of each cycle. This is the replacement for the "loop hook" with its `post_clear` argument set. - `afterEachCycle`: Called without arguments at the very end of each cycle. This is the replacement for the "loop hook" with its `post_clear` argument set.
@ -269,17 +154,8 @@ class FocusExampleCommand : public Plugin {
public: public:
FocusExampleCommand() {} FocusExampleCommand() {}
EventHandlerResult onNameQuery() { EventHandlerResult onFocusEvent(const char *command) {
return ::Focus.sendName(F("FocusExampleCommand")); if (strcmp_P(command, PSTR("example")) != 0)
}
EventHandlerResult onFocusEvent(const char *input) {
const char *cmd = PSTR("example");
if (::Focus.inputMatchesHelp(input))
return ::Focus.printHelp(cmd);
if (!::Focus.inputMatchesCommand(input, cmd))
return EventHandlerResult::OK; return EventHandlerResult::OK;
::Focus.send(F("This is an example response. Hello world!")); ::Focus.send(F("This is an example response. Hello world!"));
@ -353,8 +229,8 @@ class ExamplePlugin : public Plugin {
public: public:
ExamplePlugin(); ExamplePlugin();
EventHandlerResult onFocusEvent(const char *input) { EventHandlerResult onFocusEvent(const char *command) {
if (!::Focus.inputMatchesCommand(input, PSTR("example.toggle"))) if (strcmp_P(command, PSTR("example.toggle")) != 0)
return EventHandlerResult::OK; return EventHandlerResult::OK;
example_toggle_ = !example_toggle_; example_toggle_ = !example_toggle_;
@ -413,8 +289,8 @@ class ExampleOptionalCommand : public Plugin {
public: public:
ExampleOptionalCommand() {} ExampleOptionalCommand() {}
EventHandlerResult onFocusEvent(const char *input) { EventHandlerResult onFocusEvent(const char *command) {
if (!::Focus.inputMatchesCommand(input, PSTR("optional"))) if (strcmp_P(command, PSTR("optional")) != 0)
return EventHandlerResult::OK; return EventHandlerResult::OK;
::Focus.send(Layer.getLayerState()); ::Focus.send(Layer.getLayerState());
@ -442,383 +318,13 @@ As a developer, one can continue using `millis()`, but migrating to `Kaleidoscop
## Breaking changes ## Breaking changes
### Sketch preprocssing system
We used to support the ability to amend all compiled sketches by
adding code to
`src/kaleidoscope_internal/sketch_preprocessing/sketch_header.h`
and `src/kaleidoscope_internal/sketch_preprocessing/sketch_footer.h`.
The functionality was never used by Kaleidoscope itself and frequently
pulled the (empty) header files from the wrong copy of Kaleidoscope.
If you need this functionality, please open a GitHub issue.
### Macros
This is a guide to upgrading existing Macros code to use the new version of
Kaleidoscope and the Macros plugin.
#### New `macroAction()` function
There is a new version of the `macroAction()` function, which is the entry point
for user-defined Macros code. The old version takes two integer parameters, with
the following call signature:
```c++
const macro_t* macroAction(uint8_t macro_id, uint8_t key_state)
```
If your sketch has this function, with a `key_state` bitfield parameter, it
might still work as expected, but depending on the specifics of the code that
gets called from it, your macros might not work as expected. Either way, you
should update that function to the new version, which takes a `KeyEvent`
reference as its second parameter:
```c++
const macro_t* macroAction(uint8_t macro_id, KeyEvent &event)
```
For simple macros, it is a simple matter of replacing `key_state` in the body of
the `macroAction()` code with `event.state`. This covers most cases where all
that's done is a call to `Macros.type()`, or a `MACRO()` or `MACRODOWN()`
sequence is returned.
#### Using `MACRO()` and `MACRODOWN()`
The preprocessor macro `MACRODOWN()` has been deprecated, because the event
handler for Macros is no longer called every cycle, but only when a key is
either pressed or released. Instead of using `return MACRODOWN()`, you should
test for a toggle-on event in `macroAction()` and use `MACRO()` instead. If you
previously had something like the following in your `macroAction()` function:
```c++
switch(macro_id) {
case MY_MACRO:
return MACRODOWN(T(X), T(Y), T(Z));
}
```
...you should replace that with:
```c++
switch(macro_id) {
case MY_MACRO:
if (keyToggledOn(event.state))
return MACRO(T(X), T(Y), T(Z));
}
```
...or, for a group of macros that should only fire on keypress:
```c++
if (keyToggledOn(event.state)) {
switch(macro_id) {
case MY_MACRO:
return MACRO(T(X), T(Y), T(Z));
case MY_OTHER_MACRO:
return MACRO(T(A), T(B), T(C));
}
}
```
#### Releasing keys with `Macros.release()` or `U()`/`Ur()`/`Uc()`
Macros now operates by manipulating keys on a small supplemental virtual
keyboard when using `Macros.press()` and `Macros.release()` (which are called by
`D()` and `U()`, _et al_, respectively). This means that it has no built-in
facility for releasing other keys that are held on the keyboard. For example,
if you had a Macro that removed `shift` keycodes from the HID report in the
past, it won't work. For example:
```c++
case KEY_COMMA:
if (keyToggledOn(event.state)) {
if (Kaleidoscope.hid().keyboard().wasModifierKeyActive(Key_LeftShift)) {
return MACRO(U(LeftShift), T(Comma), D(LeftShift));
} else {
return MACRO(T(M));
}
}
```
In this case, holding a physical `Key_LeftShift` and pressing `M(KEY_COMMA)`
will not cause the held `shift` to be released, and you'll get a `<` instead of
the intended `,` (depending on the OS keymap). To accomplish this, you'll need
a small plugin like the following in your sketch:
```c++
namespace kaleidoscope {
namespace plugin {
// When activated, this plugin will suppress any `shift` key (including modifier
// combos with `shift` a flag) before it's added to the HID report.
class ShiftBlocker : public Plugin {
public:
EventHandlerResult onAddToReport(Key key) {
if (active_ && key.isKeyboardShift())
return EventHandlerResult::ABORT;
return EventHandlerResult::OK;
}
void enable() {
active_ = true;
}
void disable() {
active_ = false;
}
private:
bool active_{false};
};
} // namespace plugin
} // namespace kaleidoscope
kaleidoscope::plugin::ShiftBlocker ShiftBlocker;
```
You may also need to define a function to test for held `shift` keys:
```c++
bool isShiftKeyHeld() {
for (Key key : kaleidoscope::live_keys.all()) {
if (key.isKeyboardShift())
return true;
}
return false;
}
```
Then, in your `macroAction()` function:
```c++
if (keyToggledOn(event.state)) {
switch (macro_id) {
case MY_MACRO:
if (isShiftKeyHeld()) {
ShiftBlocker.enable();
Macros.tap(Key_Comma);
ShiftBlocker.disable();
} else {
Macros.tap(Key_M);
}
return MACRO_NONE;
}
}
```
In many simple cases, such as the above example, an even better solution is to
use the CharShift plugin instead of Macros.
#### Code that calls `handleKeyswitchEvent()` or `pressKey()`
It is very likely that if you have custom code that calls
`handleKeyswitchEvent()` or `pressKey()` directly, it will no longer function
properly after upgrading. To adapt this code to the new `KeyEvent` system
requires a deeper understanding of the changes to Kaleidoscope, but likely
results in much simpler Macros code.
The first thing that is important to understand is that the `macroAction()`
function will now only be called when a Macros `Key` toggles on or off, not once
per cycle while the key is held. This is because the new event handling code in
Kaleidoscope only calls plugin handlers in those cases, dealing with one event
at a time, in a single pass through the plugin event handlers (rather than one
pass per active key)--and only sends a keyboard HID report in response to those
events, not once per scan cycle.
This means that any Macros code that is meant to keep keycodes in the keyboard
HID report while the Macros key is held needs to be changed. For example, if a
macro contained the following code:
```c++
if (keyIsPressed(key_state)) {
Runtime.hid().keyboard().pressKey(Key_LeftShift);
}
```
...that wouldn't work quite as expected, because as soon as the next key is
pressed, a new report would be generated without ever calling `macroAction()`,
and therefore that change to the HID report would not take place, effectively
turning off the `shift` modifier immediately before sending the report with the
keycode that it was intended to modify.
Furthermore, that `shift` modifier would never even get sent in the first place,
because the HID report no longer gets cleared at the beginning of every
cycle. Now it doesn't get cleared until _after_ the plugin event handlers get
called (in the case of Macros, that's `onKeyEvent()`, which calls the
user-defined `macroAction()` function), so any changes made to the HID report
from that function will be discarded before it's sent.
Instead of the above, there are two new mechanisms for keeping keys active while
a Macros key is pressed:
##### Alter the `event.key` value
If your macro only needs to keep a single `Key` value active after running some
code, and doesn't need to run any custom code when the key is released, the
simplest thing to do is to override the event's `Key` value:
```c++
if (keyToggledOn(event.state)) {
// do some macro action(s)
event.key = Key_LeftShift;
}
```
This will (temporarily) replace the Macros key with the value assigned (in this
case, `Key_LeftShift`), starting immediately after the `macroAction()` function
returns, and lasting until the key is released. This key value can include
modifier flags, or it can be a layer-shift, or any other valid `Key` value
(though it won't get processed by plugins that are initialized before Macros in
`KALEIDOSCOPE_INIT_PLUGINS()`, and Macros itself won't act on the value, if it
gets replaced by a different Macros key).
##### Use the supplemental Macros `Key` array
The Macros plugin now contains a small array of `Key` values that will be
included when building HID reports triggered by subsequent, non-Macros
events. To use it, just call one (or more) of the following methods:
```c++
Macros.press(key);
Macros.release(key);
Macros.tap(key)
```
Each one of these functions generates a new artificial key event, and processes
it (including sending a HID report, if necessary). For `press()` and
`release()`, it also stores the specified key's value in the Macros supplemental
`Key` array. In the case of the `tap()` function, it generates matching press
and release events, but skips storing them, assuming that no plugin will
generate an intervening event. All of the events generated by these functions
will be marked `INJECTED`, which will cause Macros itself (and many other
plugins) to ignore them.
This will allow you to keep multiple `Key` values active while a Macros key is
held, while leaving the Macros key itself active, enabling more custom code to
be called on its release. Note that whenever a Macros key is released, the
supplemental key array is cleared to minimize the chances of keycodes getting
"stuck". It is still possible to write a macro that will cause values to persist
in this array, however, by combining both a sequence that uses key presses
without matched releases _and_ replacing `event.key` (see above) in the same
macro.
##### Borrow an idle key (not recommended)
It's also possible to "borrow" one (or more) idle keys on the keyboard by
searching the `live_keys[]` array for an empty entry, and generating a new event
with the address of that key. This is not recommended because surprising things
can happen if that key is then pressed and released, but it's still an option
for people who like to live dangerously.
#### Code that calls `sendReport()`
Calling `sendReport()` directly from a macro is now almost always unnecessary.
Instead, a call to `Runtime.handleKeyEvent()` will result in a keyboard HID
report being sent in response to the generated event without needing to make it
explicit.
#### Code that uses `Macros.key_addr`
This variable is deprecated. Instead, using the new `macroAction(id, event)`
function, the address of the Macros key is available via the `event.addr`
variable.
#### Working with other plugins
##### Plugin-specific `Key` values
When the the Macros plugin generates events, it marks the event state as
`INJECTED` in order to prevent unbounded recursion (Macros ignores injected
events). This causes most other plugins to ignore the event, as well.
Therefore, including a plugin-specific key (e.g. a OneShot modifier such as
`OSM(LeftAlt)`) will most likely be ignored by the target plugin, and will
therefore not have the desired effect. This applies to any calls to
`Macros.play()` (including returning `MACRO()` from `macroAction()`),
`Macros.tap()`, `Macros.press()`, and `Macros.release()`.
##### Physical event plugins
Macros cannot usefully produce events handled by plugins that implement the
`onKeyswitchEvent()` handler, such as Qukeys, TapDance, and Leader. To make
those plugins work with Macros, it's necessary to have the other plugin produce
a Macros key, not the other way around. A `macroAction()` function must not call
`Runtime.handleKeyswitchEvent()`.
##### OneShot
This is one plugin that you might specifically want to use with a macro,
generally at the end of a sequence. For example, a macro for ending one
sentence and beginning the next one might print a period followed by a space
(`. `), then a OneShot shift key tap, so that the next character will be
automatically capitalized. The problem, as mentioned before is that the
following won't work:
```c++
MACRO(Tc(Period), Tc(Spacebar), Tr(OSM(LeftShift)))
```
...because OneShot will ignore the `INJECTED` event. One solution is to change
the value of `event.key`, turning the pressed Macros key into a OneShot
modifier. This will only work if Macros is registered before OneShot in
`KALEIDOSCOPE_INIT_PLUGINS()`:
```c++
const macro_t* macroNewSentence(KeyEvent &event) {
if (keyToggledOn(event.state)) {
event.key = OSM(LeftShift);
return MACRO(Tc(Period), Tc(Spacebar));
}
return MACRO_NONE;
}
```
A more robust solution is to explicitly call `Runtime.handleKeyEvent()`, but
this is more complex, because you'll need to prevent the Macros key from
clobbering the OneShot key in the `live_keys[]` array:
```c++
void macroNewSentence(KeyEvent &event) {
if (keyToggledOn(event.state)) {
Macros.tap(Key_Period);
Macros.tap(Key_Spacebar);
event.key = OSM(LeftShift);
kaleidoscope::Runtime.handleKeyEvent(event);
// Last, we invalidate the current event's key address to prevent the Macros
// key value from clobbering the OneShot shift.
event.key = Key_NoKey;
event.addr.clear();
}
}
```
### Removed `kaleidoscope-builder`
`kaleidoscope-builder` has been removed.
We replaced it with a new Makefile based build system that uses `arduino-cli` instead of of the full Arduino IDE. This means that you can now check out development copies of Kaliedoscope into any directory, using the `KALEIDOSCOPE_DIR` environment variable to point to your installation.
### OneShot meta keys
The special OneShot keys `OneShot_MetaStickyKey` & `OneShot_ActiveStickyKey` are no longer handled by the OneShot plugin directly, but instead by a separate OneShotMetaKeys plugin. If you use these keys in your sketch, you will need to add the new plugin, and register it after OneShot in `KALEIDOSCOPE_INIT_PLUGINS()` for those keys to work properly.
### Repository rearchitecture
To improve build times and to better highlight Kaleidoscope's many plugins, plugins have been move into directories inside the Kaleidoscope directory.
The "breaking change" part of this is that git checkouts of Kaleidoscope are no longer directly compatible with the Arduino IDE, since plugins aren't in a directory the IDE looks in. They are, of course, visible to tools using our commandline build infrastructure / Makefiles.
When we build releases, those plugins are moved into directories inside the arduino platform packages for each architecture to make them visible to the Arduino IDE.
### Layer system switched to activation order ### Layer system switched to activation order
The layer system used to be index-ordered, meaning that we'd look keys up on The layer system used to be index-ordered, meaning that we'd look keys up on
layers based on the _index_ of active layers. Kaleidoscope now uses activation layers based on the _index_ of active layers. Kaleidoscope now uses activation
order, which looks up keys based on the order of layer activation. order, which looks up keys based on the order of layer activation.
The following functions have been removed as of **2021-01-01**: This means that the following functions are deprecated, and will be removed by **2020-12-31**:
- `Layer.top()`, which used to return the topmost layer index. Use - `Layer.top()`, which used to return the topmost layer index. Use
`Layer.mostRecent()` instead, which returns the most recently activated layer. `Layer.mostRecent()` instead, which returns the most recently activated layer.
@ -944,7 +450,7 @@ The new API is much shorter, and is inspired by the way the [Leader][leader]
plugin works: instead of having a list, and a dispatching function like plugin works: instead of having a list, and a dispatching function like
`magicComboActions`, we include the action method in the list too! `magicComboActions`, we include the action method in the list too!
[leader]: plugins/Kaleidoscope-Leader.md [leader]: plugins/Leader.md
We also don't make a difference between left- and right-hand anymore, you can We also don't make a difference between left- and right-hand anymore, you can
just list keys for either in the same list. This will be very handy for just list keys for either in the same list. This will be very handy for
@ -992,12 +498,6 @@ If your actions made use of the `left_hand` or `right_hand` arguments of
more involved to get to, out of scope for this simple migration guide. Please more involved to get to, out of scope for this simple migration guide. Please
open an issue, or ask for help on the forums, and we'll help you. open an issue, or ask for help on the forums, and we'll help you.
### OneShot
Older versions of the plugin were based on `Key` values; OneShot is now based on
`KeyAddr` coordinates instead, in order to improve reliability and
functionality.
### Qukeys ### Qukeys
Older versions of the plugin used `row` and `col` indexing for defining `Qukey` Older versions of the plugin used `row` and `col` indexing for defining `Qukey`
@ -1018,7 +518,7 @@ which accepts a value between 0 and 100 (interpreted as a percentage). User who
used higher values for `setReleaseDelay()` will want a lower values for used higher values for `setReleaseDelay()` will want a lower values for
`setOverlapThreshold()`. `setOverlapThreshold()`.
These functions have been removed as of **2020-12-31**: These functions have been deprecated since 2019-08-22, and will be removed by **2020-12-31**:
- `Qukeys.setTimeout(millis)` - `Qukeys.setTimeout(millis)`
- `Qukeys.setReleaseDelay(millis)` - `Qukeys.setReleaseDelay(millis)`
@ -1034,22 +534,16 @@ Storing the settable settings in EEPROM makes it depend on `Kaleidoscope-EEPROM-
Older versions of the plugin required one to set up `Key_Redial` manually, and let the plugin know about it via `Redial.key`. This is no longer required, as the plugin sets up the redial key itself. As such, `Redial.key` was removed, and `Key_Redial` is defined by the plugin itself. To upgrade, simply remove your definition of `Key_Redial` and the `Redial.key` assignment from your sketch. Older versions of the plugin required one to set up `Key_Redial` manually, and let the plugin know about it via `Redial.key`. This is no longer required, as the plugin sets up the redial key itself. As such, `Redial.key` was removed, and `Key_Redial` is defined by the plugin itself. To upgrade, simply remove your definition of `Key_Redial` and the `Redial.key` assignment from your sketch.
### Key masking has been removed ### Key masking has been deprecated
Key masking was a band-aid introduced to avoid accidentally sending unintended keys when key mapping changes between a key being pressed and released. Since the introduction of keymap caching, this is no longer necessary, as long as we can keep the mapping consistent. Users of key masking are encouraged to find ways to use the caching mechanism instead. Key masking was a band-aid introduced to avoid accidentally sending unintended keys when key mapping changes between a key being pressed and released. Since the introduction of keymap caching, this is no longer necessary, as long as we can keep the mapping consistent. Users of key masking are encouraged to find ways to use the caching mechanism instead.
As an example, if you had a key event handler that in some cases masked a key, it should now map it to `Key_NoKey` instead, until released. As an example, if you had a key event handler that in some cases masked a key, it should now map it to `Key_NoKey` instead, until released.
The masking API has been removed on **2021-01-01** The masking API has been deprecated, and is scheduled to be removed after **2020-11-25**.
## Deprecated APIs and their replacements ## Deprecated APIs and their replacements
### Leader plugin
The `Leader.inject()` function is deprecated. Please call `Runtime.handleKeyEvent()` directly instead.
Direct access to the `Leader.time_out` configuration variable is deprecated. Please use the `Leader.setTimeout(ms)` function instead.
### Source code and namespace rearrangement ### Source code and namespace rearrangement
With the move towards a monorepo-based source, some headers have moved to a new location, and plenty of plugins moved to a new namespace (`kaleidoscope::plugin`). This means that the old headers, and some old names are deprecated. The old names no longer work. With the move towards a monorepo-based source, some headers have moved to a new location, and plenty of plugins moved to a new namespace (`kaleidoscope::plugin`). This means that the old headers, and some old names are deprecated. The old names no longer work.
@ -1057,121 +551,29 @@ With the move towards a monorepo-based source, some headers have moved to a new
The following headers and names have changed: The following headers and names have changed:
- `layers.h`, `key_defs_keymaps.h` and `macro_helpers.h` are obsolete, and should not be included in the first place, as `Kaleidoscope.h` will pull them in. In the rare case that one needs them, prefixing them with `kaleidoscope/` is the way to go. Of the various headers provided under the `kaleidoscope/` space, only `kaleidoscope/macro_helpers.h` should be included directly, and only by hardware plugins that can't pull `Kaleidoscope.h` in due to circular dependencies. - `layers.h`, `key_defs_keymaps.h` and `macro_helpers.h` are obsolete, and should not be included in the first place, as `Kaleidoscope.h` will pull them in. In the rare case that one needs them, prefixing them with `kaleidoscope/` is the way to go. Of the various headers provided under the `kaleidoscope/` space, only `kaleidoscope/macro_helpers.h` should be included directly, and only by hardware plugins that can't pull `Kaleidoscope.h` in due to circular dependencies.
- `LED-Off.h`, provided by [LEDControl](plugins/Kaleidoscope-LEDControl.md) is obsolete, the `LEDOff` LED mode is automatically provided by `Kaleidoscope-LEDControl.h`. The `LED-Off.h` includes can be safely removed. - `LED-Off.h`, provided by [LEDControl](plugins/LEDControl.md) is obsolete, the `LEDOff` LED mode is automatically provided by `Kaleidoscope-LEDControl.h`. The `LED-Off.h` includes can be safely removed.
- `LEDUtils.h` is automatically pulled in by `Kaleiodscope-LEDControl.h`, too, and there's no need to directly include it anymore. - `LEDUtils.h` is automatically pulled in by `Kaleiodscope-LEDControl.h`, too, and there's no need to directly include it anymore.
- Plugins that implement LED modes should subclass `kaleidoscope::plugin::LEDMode` instead of `kaleidoscope::LEDMode`. - Plugins that implement LED modes should subclass `kaleidoscope::plugin::LEDMode` instead of `kaleidoscope::LEDMode`.
- [GhostInTheFirmware](plugins/Kaleidoscope-GhostInTheFirmware.md) had the `kaleidoscope::GhostInTheFirmware::GhostKey` type replaced by `kaleidoscope::plugin::GhostInTheFirmware::GhostKey`. - [GhostInTheFirmware](plugins/GhostInTheFirmware.md) had the `kaleidoscope::GhostInTheFirmware::GhostKey` type replaced by `kaleidoscope::plugin::GhostInTheFirmware::GhostKey`.
- [HostOS](plugins/Kaleidoscope-HostOS.md) no longer provides the `Kaleidoscope/HostOS-select.h` header, and there is no backwards compatibility header either. - [HostOS](plugins/HostOS.md) no longer provides the `Kaleidoscope/HostOS-select.h` header, and there is no backwards compatibility header either.
- [Leader](plugins/Kaleidoscope-Leader.md) had the `kaleidoscope::Leader::dictionary_t` type replaced by `kaleidoscope::plugin::Leader::dictionary_t`. - [Leader](plugins/Leader.md) had the `kaleidoscope::Leader::dictionary_t` type replaced by `kaleidoscope::plugin::Leader::dictionary_t`.
- [LED-AlphaSquare](plugins/Kaleidoscope-LED-AlphaSquare.md) used to provide extra symbol graphics in the `kaleidoscope::alpha_square::symbols` namespace. This is now replaced by `kaleidoscope::plugin::alpha_square::symbols`. - [LED-AlphaSquare](plugins/LED-AlphaSquare.md) used to provide extra symbol graphics in the `kaleidoscope::alpha_square::symbols` namespace. This is now replaced by `kaleidoscope::plugin::alpha_square::symbols`.
- [LEDEffect-SolidColor](plugins/Kaleidoscope-LEDEffect-SolidColor.md) replaced the base class - `kaleidoscope::LEDSolidColor` - with `kaleidoscope::plugin::LEDSolidColor`. - [LEDEffect-SolidColor](plugins/LEDEffect-SolidColor.md) replaced the base class - `kaleidoscope::LEDSolidColor` - with `kaleidoscope::plugin::LEDSolidColor`.
- [Qukeys](plugins/Kaleidoscope-Qukeys.md) had the `kaleidoscope::Qukey` type replaced by `kaleidoscope::plugin::Qukey`. - [Qukeys](plugins/Qukeys.md) had the `kaleidoscope::Qukey` type replaced by `kaleidoscope::plugin::Qukey`.
- [ShapeShifter](plugins/Kaleidoscope-ShapeShifter.md) had the `kaleidoscope::ShapeShifter::dictionary_t` type replaced by `kaleidoscope::plugin::ShapeShifter::dictionary_t`. - [ShapeShifter](plugins/ShateShifter.md) had the `kaleidoscope::ShapeShifter::dictionary_t` type replaced by `kaleidoscope::plugin::ShapeShifter::dictionary_t`.
- [SpaceCadet](plugins/Kaleidoscope-SpaceCadet.md) had the `kaleidoscope::SpaceCadet::KeyBinding` type replaced by `kaleidoscope::plugin::SpaceCadet::KeyBinding`. - [SpaceCadet](plugins/SpaceCadet.md) had the `kaleidoscope::SpaceCadet::KeyBinding` type replaced by `kaleidoscope::plugin::SpaceCadet::KeyBinding`.
- [Syster](plugins/Kaleidoscope-Syster.md) had the `kaleidoscope::Syster::action_t` type replaced by `kaleidoscope::plugin::Syster::action_t`. - [Syster](plugins/Syster.md) had the `kaleidoscope::Syster::action_t` type replaced by `kaleidoscope::plugin::Syster::action_t`.
- [TapDance](plugins/Kaleidoscope-TapDance.md) had the `kaleidoscope::TapDance::ActionType` type replaced by `kaleidoscope::plugin::TapDance::ActionType`. - [TapDance](plugins/TapDance.md) had the `kaleidoscope::TapDance::ActionType` type replaced by `kaleidoscope::plugin::TapDance::ActionType`.
# Removed APIs # Removed APIs
### Removed on 2022-03-03
#### Pre-`KeyEvent` event handler hooks
The old event handler `onKeyswitchEvent(Key &key, KeyAddr addr, uint8_t state)` was removed on **2022-03-03**. It has been replaced with the new `onKeyEvent(KeyEvent &event)` handler (and, in some special cases the `onKeyswitchEvent(KeyEvent &event)` handler). Plugins using the deprecated handler will need to be rewritten to use the new one(s).
The old event handler `beforeReportingState()` was removed on **2022-03-03**. It has been replaced with the new `beforeReportingState(KeyEvent &event)` handler. However, the new handler will be called only when a report is being sent (generally in response to a key event), not every cycle, like the old one. It was common practice in the past for plugins to rely on `beforeReportingState()` being called every cycle, so when adapting to the `KeyEvent` API, it's important to check for code that should be moved to `afterEachCycle()` instead.
#### `::handleKeyswitchEvent(Key key, KeyAddr key_addr, uint8_t state)`
The old master function for processing key "events" was removed on **2022-03-03**. Functions that were calling this function should be rewritten to call `kaleidoscope::Runtime.handleKeyEvent(KeyEvent event)` instead.
#### `Keyboard::pressKey(Key key, bool toggled_on)`
This deprecated function was removed on **2022-03-03**. Its purpose was to handle rollover events for keys that include modifier flags, and that handling is now done elsewhere. Any code that called it should now simply call `Keyboard::pressKey(Key key)` instead, dropping the second argument.
#### Old layer key event handler functions
The deprecated `Layer.handleKeymapKeyswitchEvent()` function was removed on **2022-03-03**. Any code that called it should now call `Layer.handleLayerKeyEvent()` instead, with `event.addr` set to the appropriate `KeyAddr` value if possible, and `KeyAddr::none()` otherwise.
The deprecated `Layer.eventHandler(key, addr, state)` function was removed on **2022-03-03**. Any code that refers to it should now call call `handleLayerKeyEvent(KeyEvent(addr, state, key))` instead.
#### Keymap cache functions
The deprecated `Layer.updateLiveCompositeKeymap()` function was removed on **2022-03-03**. Plugin and user code probably shouldn't have been calling this directly, so there's no direct replacement for it. If a plugin needs to make changes to the `live_keys` structure (equivalent in some circumstances to the old "live composite keymap"), it can call `live_keys.activate(addr, key)`, but there are probably better ways to accomplish this goal (e.g. simply changing the value of `event.key` from an `onKeyEvent(event)` handler).
The deprecated `Layer.lookup(addr)` function was removed on **2022-03-03**. Please use `Runtime.lookupKey(addr)` instead in most circumstances. Alternatively, if you need information about the current state of the keymap regardless of any currently active keys (which may have values that override the keymap), use `Layer.lookupOnActiveLayer(addr)` instead.
#### `LEDControl.syncDelay` configuration variable
Direct access to this configuration variable was removed on **2022-03-03**. Please use `LEDControl.setInterval()` to set the interval between LED updates instead.
#### Obsolete active macros array removed
The deprecated `Macros.active_macro_count` variable was removed on **2022-03-03**. Any references to it are obsolete, and can simply be removed.
The deprecated `Macros.active_macros[]` array was removed on **2022-03-03**. Any references to it are obsolete, and can simply be removed.
The deprecated `Macros.addActiveMacroKey()` function was removed on **2022-03-03**. Any references to it are obsolete, and can simply be removed.
#### Pre-`KeyEvent` Macros API
This is a brief summary of specific elements that were removed. There is a more comprehensive guide to upgrading existing Macros user code in the [Breaking Changes](#breaking-changes) section, under [Macros](#macros).
Support for deprecated form of the `macroAction(uint8_t macro_id, uint8_t key_state)` function was removed on **2022-03-03**. This old form must be replaced with the new `macroAction(uint8_t macro_id, KeyEvent &event)` for macros to continue working.
The `Macros.key_addr` public variable was removed on **2022-03-03**. To get access to the key address of a Macros key event, simply refer to `event.addr` from within the new `macroAction(macro_id, event)` function.
The deprecated `MACRODOWN()` preprocessor macro was removed on **2022-03-03**. Since most macros are meant to be triggered only by keypress events (not key release), and because `macroAction()` does not get called every cycle for held keys, it's better to simply do one test for `keyToggledOn(event.state)` first, then use `MACRO()` instead.
#### ActiveModColor public variables
The following deprecated `ActiveModColorEffect` public variables were removed on **2022-03-03**. Please use the following methods instead:
- For `ActiveModColor.highlight_color`, use `ActiveModColor.setHighlightColor(color)`
- For `ActiveModColor.oneshot_color`, use `ActiveModColor.setOneShotColor(color)`
- For `ActiveModColor.sticky_color`, use `ActiveModColor.setStickyColor(color)`
#### OneShot public variables
The following deprecated `OneShot` public variables were removed on **2022-03-03**. Please use the following methods instead:
- For `OneShot.time_out`, use `OneShot.setTimeout(ms)`
- For `OneShot.hold_time_out`, use `OneShot.setHoldTimeout(ms)`
- For `OneShot.double_tap_time_out`, use `OneShot.setDoubleTapTimeout(ms)`
#### Deprecated OneShot API functions
OneShot was completely rewritten in early 2021, and now is based on `KeyAddr` values (as if it keeps physical keys pressed) rather than `Key` values (with no corresponding physical key location). This allows it to operate on any `Key` value, not just modifiers and layer shifts.
The deprecated `OneShot.inject(key, key_state)` function was removed on **2022-03-03**. Its use was very strongly discouraged, and is now unavailable. See below for alternatives.
The deprecated `OneShot.isActive(key)` function was removed on **2022-03-03**. There is a somewhat equivalent `OneShot.isActive(KeyAddr addr)` function to use when the address of a key that might be currently held active by OneShot is known. Any code that needs information about active keys is better served by not querying OneShot specifically.
The deprecated `OneShot.isSticky(key)` function was removed on **2022-03-03**. There is a somewhat equivalent `OneShot.isStick(KeyAddr addr)` function to use when the address of a key that may be in the one-shot sticky state is known.
The deprecated `OneShot.isPressed()` function was removed on **2022-03-03**. It was already devoid of functionality, and references to it can be safely removed.
The deprecated `OneShot.isModifierActive(key)` function was removed on **2022-03-03**. OneShot modifiers are now indistinguishable from other modifier keys, so it is better for client code to do a more general search of `live_keys` or to use another mechanism for tracking this state.
#### `HostPowerManagement.enableWakeup()`
This deprecated function was removed on **2022-03-03**. The firmware now supports wakeup by default, so any references to it can be safely removed.
#### `EEPROMSettings.version(uint8_t version)`
This deprecated function was removed on **2022-03-03**. The information stored is not longer intended for user code to set, but instead is used internally.
#### Model01-TestMode plugin
This deprecated plugin was removed on **2022-03-03**. Please use the more generic HardwareTestMode plugin instead.
### Removed on 2020-10-10 ### Removed on 2020-10-10
#### Deprecation of the HID facade ### Deprecation of the HID facade
With the new Device APIs it became possible to replace the HID facade (the `kaleidoscope::hid` family of functions) with a driver. As such, the old APIs are deprecated, and was removed on 2020-10-10. Please use `Kaleidoscope.hid()` instead. With the new Device APIs it became possible to replace the HID facade (the `kaleidoscope::hid` family of functions) with a driver. As such, the old APIs are deprecated, and was removed on 2020-10-10. Please use `Kaleidoscope.hid()` instead.
#### Implementation of type Key internally changed from C++ union to class ### Implementation of type Key internally changed from C++ union to class
The deprecated functions were removed on 2020-10-10. The deprecated functions were removed on 2020-10-10.
@ -1242,7 +644,7 @@ The deprecated row/col based indexing APIs have been removed on **2020-06-16**.
#### EEPROMKeymap mode #### EEPROMKeymap mode
The [EEPROM-Keymap](plugins/Kaleidoscope-EEPROM-Keymap.md) plugin had its `setup()` method changed, the formerly optional `method` argument is now obsolete and unused. It can be safely removed. The [EEPROM-Keymap](plugins/EEPROM-Keymap.md) plugin had its `setup()` method changed, the formerly optional `method` argument is now obsolete and unused. It can be safely removed.
##### keymaps array and KEYMAPS and KEYMAPS_STACKED macros ##### keymaps array and KEYMAPS and KEYMAPS_STACKED macros

@ -70,7 +70,19 @@ def find_examples_docs(src_dir, target_dir):
def config_inited(app, config): def config_inited(app, config):
os.system('make examples') # Handle configuration settings
source_path = Path(app.srcdir, app.config.examples_source).resolve()
dest_path = Path(app.srcdir, app.config.examples_dest).resolve()
print (dest_path)
def docname(item):
"""Helper for status_iterator()."""
return str(Path(item[0]).relative_to(source_path).parent)
if os.path.isdir(dest_path):
rmtree(dest_path)
copytree(source_path, dest_path)
def setup(app): def setup(app):
app.add_config_value('examples_source', None, 'env', (PathLike, str))
app.add_config_value('examples_dest', None, 'env', (PathLike, str))
app.connect('config-inited', config_inited) app.connect('config-inited', config_inited)

@ -1,11 +0,0 @@
"""Crude Sphinx extension to run a makefile step
"""
import os
def config_inited(app, config):
os.system('make copy-plugin-readmes')
def setup(app):
app.connect('config-inited', config_inited)

@ -27,13 +27,13 @@ amount of custom code one has to write will be minimal.
A `Device` is the topmost level component, it is the interface the rest of A `Device` is the topmost level component, it is the interface the rest of
Kaleidoscope will work with. The [`kaleidoscope::device::Base`][k:d:Base] class Kaleidoscope will work with. The [`kaleidoscope::device::Base`][k:d:Base] class
is the ancestor of _all_ devices, everything derives from this. Devices that use is the ancestor of _all_ devices, everything derives from this. Devices that use
an `ATmega32U4` MCU we also have the an `ATMega32U4` MCU we also have the
[`kaleidoscope::device::ATmega32U4Keyboard`][k:d:a32u4] class, which sets up [`kaleidoscope::device::ATMega32U4Keyboard`][k:d:a32u4] class, which sets up
some of the components that is common to all `ATmega32U4`-based devices (such as some of the components that is common to all `ATMega32U4`-based devices (such as
the _MCU_ and the _Storage_). the _MCU_ and the _Storage_).
[k:d:Base]: ../../src/kaleidoscope/device/Base.h [k:d:Base]:../src/kaleidoscope/device/Base.h
[k:d:a32u4]: ../../src/kaleidoscope/device/ATmega32U4Keyboard.h [k:d:a32u4]: ../src/kaleidoscope/device/ATMega32U4.h
As hinted at above, a device - or rather, it's `Props` - describe the components As hinted at above, a device - or rather, it's `Props` - describe the components
used for the device, such as the MCU, the Bootloader, the Storage driver, LEDs, used for the device, such as the MCU, the Bootloader, the Storage driver, LEDs,
@ -42,7 +42,7 @@ in `Props` - the defaults are all no-ops.
All devices must also come with a `Props` struct, deriving from [`kaleidoscope::device::BaseProps`][k:d:BaseProps]. All devices must also come with a `Props` struct, deriving from [`kaleidoscope::device::BaseProps`][k:d:BaseProps].
[k:d:BaseProps]: ../../src/kaleidoscope/device/Base.h [k:d:BaseProps]: ../src/kaleidoscope/device/Base.h
As an example, the most basic device we can have, that does nothing, would look As an example, the most basic device we can have, that does nothing, would look
like this: like this:
@ -66,16 +66,16 @@ components the device ends up using.
The heart of any device will be the main controller unit, or _MCU_ for short. The heart of any device will be the main controller unit, or _MCU_ for short.
The [`kaleidoscope::driver::mcu::Base`][k:d:m:Base] class is the ancestor of our The [`kaleidoscope::driver::mcu::Base`][k:d:m:Base] class is the ancestor of our
MCU drivers, including [`kaleidoscope::driver::mcu::ATmega32U4`][k:d:m:a32u4]. MCU drivers, including [`mcu::ATMega32U4`][k:d:m:a32u4].
[k:d:m:Base]: ../../src/kaleidoscope/driver/mcu/Base.h [k:d:m:Base]: ../src/kaleidoscope/driver/mcu/Base.h
[k:d:m:a32u4]: ../../src/kaleidoscope/driver/mcu/ATmega32U4.h [k:d:m:a32u4]: ../src/kaleidoscope/driver/mcu/ATMega32U4.h
The core firmware will use the `detachFromHost()` and `attachToHost()` methods The core firmware will use the `detachFromHost()` and `attachToHost()` methods
of the MCU driver, along with `setup()`, but the driver - like any other of the MCU driver, along with `setup()`, but the driver - like any other
driver - is free to have other methods, to be used by individual devices. driver - is free to have other methods, to be used by individual devices.
For example, the [`ATmega32U4`][k:d:m:a32u4] driver implements a `disableJTAG()` For example, the [`ATMega32U4`][k:d:m:a32u4] driver implements a `disableJTAG()`
and a `disableClockDivision()` method, which some of our devices use in their and a `disableClockDivision()` method, which some of our devices use in their
constructors. constructors.
@ -88,16 +88,16 @@ thing that allows us to re-program the keyboard without additional hardware
(aptly called a programmer). As such, the [`base class`][k:d:b:Base] has a (aptly called a programmer). As such, the [`base class`][k:d:b:Base] has a
single method, `rebootBootloader()`, which our bootloader components implement. single method, `rebootBootloader()`, which our bootloader components implement.
[k:d:b:Base]: ../../src/kaleidoscope/driver/bootloader/Base.h [k:d:b:Base]: ../src/kaleidoscope/bootloader/Base.h
Kaleidoscope currently supports [`Caterina`][k:d:b:Caterina], Kaleidoscope currently supports [`Catalina`][k:d:b:Catalina],
[`HalfKay`][k:d:b:HalfKay], and [`FLIP`][k:d:b:FLIP] bootloaders. Please consult [`HalfKay`][k:d:b:HalfKay], and [`FLIP`][k:d:b:FLIP] bootloaders. Please consult
them for more information. In many cases, setting up the bootloader in the them for more information. In many cases, setting up the bootloader in the
device props is all one needs to do. device props is all one needs to do.
[k:d:b:Caterina]: ../../src/kaleidoscope/driver/bootloader/avr/Caterina.h [k:d:b:Catalina]: ../src/kaleidoscope/driver/bootloader/avr/Catalina.h
[k:d:b:HalfKay]: ../../src/kaleidoscope/driver/bootloader/avr/HalfKay.h [k:d:b:HalfKay]: ../src/kaleidoscope/driver/bootloader/avr/HalfKay.h
[k:d:b:FLIP]: ../../src/kaleidoscope/driver/bootloader/avr/FLIP.h [k:d:b:FLIP]: ../src/kaleidoscope/driver/bootloader/avr/FLIP.h
Like the _MCU_ component, the _bootloader_ does not use Props, either. Like the _MCU_ component, the _bootloader_ does not use Props, either.
@ -113,40 +113,40 @@ to flash new firmware.
The Storage API resembles the Arduino EEPROM API very closely. In fact, our The Storage API resembles the Arduino EEPROM API very closely. In fact, our
[`AVREEPROM`][k:d:s:AVREEPROM] class is but a thin wrapper around that! [`AVREEPROM`][k:d:s:AVREEPROM] class is but a thin wrapper around that!
[k:d:s:Base]: ../../src/kaleidoscope/driver/storage/Base.h [k:d:s:Base]: ../src/kaleidoscope/driver/storage/Base.h
[chrysalis]: https://github.com/keyboardio/Chrysalis [chrysalis]: https://github.com/keyboardio/Chrysalis
[k:d:s:AVREEPROM]: ../../src/kaleidoscope/driver/storage/AVREEPROM.h [k:d:s:AVREEPROM]: ../src/kaleidoscope/driver/storage/AVREEPROM.h
The `Storage` component does use Props, one that describes the length - or The `Storage` component does use Props, one that describes the length - or
size - of it. We provide an [`ATmega32U4EEPROMProps`][k:d:s:a32u4props] helper, size - of it. We provide an [`ATMega32U4EEPROMProps`][k:d:s:a32u4props] helper,
which is preconfigured for the 1k EEPROM size of the ATmega32U4. which is preconfigured for the 1k EEPROM size of the ATMega32U4.
[k:d:s:a32u4props]: ../../src/kaleidoscope/driver/storage/ATmega32U4EEPROMProps.h [k:d:s:a32u4props]: ../src/kaleidoscope/driver/storage/ATMega32U4EEPROMProps.h
### LEDs ### LEDs
[`kaleidoscope::driver::led::Base`][k:d:l:Base] [`kaleidoscope::driver::led::Base`][k:d:l:Base]
[k:d:l:Base]: ../../src/kaleidoscope/driver/led/Base.h [k:d:l:Base]: ../src/kaleidoscope/driver/led/Base.h
### Keyscanner ### Keyscanner
[`kaleidoscope::driver::keyscanner::Base`][k:d:ks:Base] [`kaleidoscope::driver::keyscanner::Base`][k:d:ks:Base]
[k:d:ks:Base]: ../../src/kaleidoscope/driver/keyscanner/Base.h [k:d:ks:Base]: ../src/kaleidoscope/driver/keyscanner/Base.h
## Helpers ## Helpers
[`kaleidoscope::device::ATmega32U4Keyboard`][k:d:a32u4k] [`kaleidoscope::device::ATMega32U4Keyboard`][k:d:a32u4k]
[`kaleidoscope::driver::keyscanner::ATmega`][k:d:ks:atm] [`kaleidoscope::driver::keyscanner::AVR`][k:d:ks:avr]
[k:d:a32u4k]: ../../src/kaleidoscope/device/ATmega32U4Keyboard.h [k:d:a32u4k]: ../src/kaleidoscope/device/ATMega32U4Keyboard.h
[k:d:ks:atm]: ../../src/kaleidoscope/driver/keyscanner/ATmega.h [k:d:ks:avr]: ../src/kaleidoscope/driver/keyscanner/AVR.h
## Putting it all together ## Putting it all together
To put things into perspective, and show a simple example, we'll build an To put things into perspective, and show a simple example, we'll build an
imaginary mini keypad: `ATmega32U4` with `Caterina` as bootloader, no LEDs, and imaginary mini keypad: `ATMega32U4` with `Caterina` as bootloader, no LEDs, and
four keys only. four keys only.
### `ImaginaryKeypad.h` ### `ImaginaryKeypad.h`
@ -157,9 +157,9 @@ four keys only.
#ifdef ARDUINO_AVR_IMAGINARY_KEYPAD #ifdef ARDUINO_AVR_IMAGINARY_KEYPAD
#include <Arduino.h> #include <Arduino.h>
#include "kaleidoscope/driver/keyscanner/ATmega.h" #include "kaleidoscope/driver/keyscanner/AVR.h"
#include "kaleidoscope/driver/bootloader/avr/Caterina.h" #include "kaleidoscope/driver/bootloader/avr/Caterina.h"
#include "kaleidoscope/device/ATmega32U4Keyboard.h" #include "kaleidoscope/device/ATMega32U4Keyboard.h"
namespace kaleidoscope { namespace kaleidoscope {
namespace device { namespace device {
@ -175,7 +175,7 @@ struct KeypadProps : kaleidoscope::device::ATmega32U4KeyboardProps {
}; };
typedef kaleidoscope::driver::keyscanner::ATmega<KeyScannerProps> KeyScanner; typedef kaleidoscope::driver::keyscanner::ATmega<KeyScannerProps> KeyScanner;
typedef kaleidoscope::driver::bootloader::avr::Caterina Bootloader; typedef kaleidoscope::driver::bootloader::avr::Caterina BootLoader;
static constexpr const char *short_name = "imaginary-keypad"; static constexpr const char *short_name = "imaginary-keypad";
}; };

@ -1,257 +0,0 @@
# Kaleidoscope's Plugin Event Handlers
Kaleidoscope provides a set of hook functions that plugins can define in order
to do their work. If one or more of the functions listed here are defined as
methods in a plugin class, that plugin can act on the input events that drive
Kaleidoscope.
In response to input events (plus a few other places), Kaleidoscope calls the
event handlers for each plugin that defines them, in sequence.
## Return values
Every Kaleidoscope event handler function returns a value of type
`EventHandlerResult`, an enum with several variants. In some handlers,
Kaleidoscope ignores the return value, but for others, the result is used as a
signal to control Kaleidoscope's behavior. In particular, some event handler
hooks are "abortable". For those hooks, the return value of the plugin handlers
are used to control what Kaleidoscope does after each plugin's event handler
returns.
- `EventHandlerResult::OK` is used to signal that Kaleidoscope should continue
on to the next handler in the sequence.
- `EventHandlerResult::ABORT` is used to signal that Kaleidoscope should not
continue to call the other plugin handlers in the sequence, and stop
processing the event entirely. This is used by some plugins to cancel events
and/or delay them so that they occur at a later time, possibly with different
values.
- `EventHandlerResult::EVENT_CONSUMED` is used to signal that the plugin has
successfully handled the event, and that there is nothing further to be done,
so there is no point in continuing to call further plugin event handlers for
the event.
## Non-event "event" handlers
There are three special "event" handlers that are not called in response to
input events, but are instead called at fixed points during Kaleidoscope's run
time.
### `onSetup()`
This handler is called when Kaleidoscope first starts. If a plugin needs to do
some work after its constructor is called, but before Kaleidoscope enters its
main loop and starts scanning for keyswitch events, it can do it in this
function.
### `beforeEachCycle()`
This handler gets called at the beginning of every keyswitch scan cycle, before
the scan. It can be used by plugins to do things that need to be done
repeatedly, regardless of any input from the user. Typically, this involves
things like checking for timeouts.
### `afterEachCycle()`
This is just like `beforeEachCycle()`, but gets called after the keyswitches
have been scanned (and any input events handled).
## Keyswitch input event handlers
This group of event handlers is triggered when keys on the keyboard are pressed
and released. With one exception, they use a `KeyEvent` object as their one
parameter. The `KeyEvent` class encapsulates the essential data about a key
press (or release):
- `event.addr` contains the `KeyAddr` of the key that toggled on or off.
- `event.state` contains information about the current and former state of the
key in the form of a `uint8_t` bitfield.
- `event.key` contains the `Key` value of the event. For key presses, this is
generally determined by means of a keymap lookup. For releases, the value is
taken from the `live_keys` structure. Because the `event` is passed by
reference, changing this value in a plugin handler will affect which value
ends up in the `live_keys` array, and thus, the output of the keyboard.
- `event.id` contains a `KeyEventId` value: an integer, usually monotonically
increasing. This is useful as a tool to allow plugins to avoid re-processing
the same event, thus avoiding infinite loops without resorting to an
`INJECTED` key state flag which would cause other plugins to ignore events
that they might otherwise be interested in.
### `onKeyswitchEvent(KeyEvent &event)`
This handler is called in response to changes detected in the state of
keyswitches, via the `Runtime.handleKeyswitchEvent()` function. After the
keyswitches are scanned in each cycle, Kaleidoscope goes through them all and
compares the state of each one to its previous state. For any of them that have
either toggled on or off, plugins that define this function get called (until
one of them returns either `ABORT` or `EVENT_CONSUMED`).
This handler should be defined by any plugin that is concerned only with
physical keyswitch events, where the user has pressed or released a physical
key. For example, plugins that determine key values based on the timing of these
physical events should define this handler (for example, Qukeys and
TapDance). Plugins that don't explicitly need to use this handler should define
`onKeyEvent()` instead.
Plugins that use this handler should abide by certain rules in order to interact
with each other to avoid infinite loops. A plugin might return `ABORT` to delay
an event (until some other event or a timeout occurs), then later re-start
processing of the same event by calling `Runtime.handleKeyswitchEvent()`. When
it does this, it must take care to use the same `KeyEventId` value as that
event's `id` parameter, and it should also take care to preserve the order of
any such events. This way, plugins implementing `onKeyswitchEvent()` are able
to keep track of event id numbers that they have already processed fully, and
ignore those events when plugins later in the sequence re-start them.
In more specific detail, plugins that implement `onKeyswitchEvent()` must
guarantee that the `event.id` values they emit when returning `OK` are
monotonically increasing, and should only include `id` values that the plugin
has already received as input. Additionally, such plugins must ignore any event
with an `id` value that it has recently received and finished processing. The
class `KeyEventTracker` can help simplify following these rules.
### `onKeyEvent(KeyEvent &event)`
After a physical keyswitch event is processed by all of the plugins with
`onKeyswitchEvent()` handlers (and they all return `OK`), Kaleidoscope passes
that event on to the `Runtime.handleKeyEvent()` function, which calls plugins'
`onKeyEvent()` handlers. This is also the starting point for events which do not
correspond to physical key events, and can have an invalid `event.addr` value.
Plugins that need to respond to keyboard input, but which do not need to be
closely tied to physical key events (and only those events) should use
`onKeyEvent()` to do their work.
After all `onKeyEvent()` handlers have returned `OK` for an event, the
`live_keys` state array gets updated. For a key press event, the final
`event.key` value gets inserted into `live_keys[event.addr]`. From that point
on, the keyboard will behave as though a key with that value is being held until
that entry in `live_keys` is cleared (most likely as a result of a key release
event's `onKeyEvent()` handlers returning `OK`). Thus, if an `onKeyEvent()`
handler returns `ABORT` for a key release event, the keyboard will behave as
though that key is still held after it has been released. This is what enables
plugins like OneShot to function, but it also means that plugin authors need to
take care about returning `ABORT` (but not `EVENT_CONSUMED`) from an
`onKeyEvent()` handler, because it could result in "stuck" keys.
`onKeyEvent()` handlers should not store events and release them later (by
calling `Runtime.handleKeyEvent()`), and must never call
`Runtime.handleKeyswitchEvent()`.
### `onAddToReport(Key key)`
After the `onKeyEvent()` handlers have all returned `OK`, Kaleidoscope moves on
to sending Keyboard HID reports. It clears the current report, and iterates
through the `live_keys` array, looking for non-empty values, and adding them to
the report. For System Control, Consumer Control, and Keyboard HID type `Key`
values, Kaleidoscope handles adding the keycodes to the correct report, but it
also calls this handler, in case a plugin needs to alter that report.
A return value of `OK` allows Kaleidoscope to proceed with adding the
corresponding keycode(s) to the HID report, and `ABORT` causes it to leave and
keycodes from `key` out of the report.
Note that this only applies to the Keyboard and Consumer Control HID reports,
not the System Control report, which has different semantics, and only supports
a single keycode at a time.
### `beforeReportingState(const KeyEvent &event)`
This gets called right before a set of HID reports is sent. At this point,
plugins have access to a (tentative) complete HID report, as well as the full
state of all live keys on the keyboard. This is especially useful for plugins
that might need to do things like remove keycodes (such as keyboard modifiers)
from the forthcoming report just before it gets sent.
This event handler still has access to the event information for the event that
triggered the report, but because it is passed as a `const` reference, it is no
longer possible to change any of its values.
[Note: The older version of `beforeReportingState()` got called once per cycle,
regardless of the pattern of keyswitches toggling on and off, and many plugins
used it as a place to do things like check for timeouts. This new version does
not get called every cycle, so when porting old code to the newer handlers, it's
important to move any code that must be called every cycle to either
`beforeEachCycle()` or `afterEachCycle()`.]
[Also note: Unlike the deprecated `beforeReportingState()`, this one is
abortable. That is, if it returns a result other than `OK` it will stop the
subsequent handlers from getting called, and if it returns `ABORT`, it will also
stop the report from being sent.]
### `afterReportingState(const KeyEvent &event)`
This gets called after the HID report is sent. This handler allows a plugin to
react to an event, but wait until after that event has been fully processed to
do so. For example, the OneShot plugin releases keys that are in the "one-shot"
state in response to key press events, but it does so after those triggering
press events take place.
## Other events
### `onLayerChange()`
Called whenever one or more keymap layers are activated or deactivated (just
after the change takes place).
### `onLEDModeChange()`
Called by `LEDControl` whenever the active LED mode changes.
### `beforeSyncingLeds()`
Called immediately before Kaleidoscope sends updated color values to the
LEDs. This event handler is particularly useful to plugins that need to override
the active LED mode (e.g. LED-ActiveModColor).
### `onFocusEvent()`
### `onNameQuery()`
### `exploreSketch()`
## Deprecated
Two existing "event" handlers have been deprecated. In the old version of
Kaleidoscope's main loop, the keyboard's state information was stored in the
keyscanner (which physical switches were on in the current and former scans),
and in the HID reports. The Keyboard HID report would be cleared at the start of
every cycle, and re-populated, on key at a time, calling every
`onKeyswitchEvent()` handler for every active key. Then, once the tentative HID
report was complete, the `beforeReportingState()` handlers would be called, and
the complete report would be sent to the host. In most cycles, that report would
be identical to the previous report, and would be suppressed.
The new system stores the keyboard's current state in the `live_keys` array
instead, and only calls event handlers in response to keyswitch state changes
(and artificially generated events), ultimately sending HID reports in response
to events, rather than at the end of every cycle.
### `onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state)`
This handler was called in every cycle, for every non-idle key. Its concept of
an "event" included held keys that did not have a state change. These deprecated
handlers are still called, in response to events and also when preparing the HID
reports, but there is no longer a reasonable mechanism to call them in every
cycle, for every active key, so some functionality could be lost.
It is strongly recommended to switch to using one of the two `KeyEvent`
functions instead, depending on the needs of the plugin (either `onKeyEvent()`
if it is fit for the purpose, or `onKeyswitchEvent()` if necessary). The
`onAddToReport()` function might also be useful, particularly if the plugin in
question uses special `Key` values not recognized by Kaleidoscope itself, but
which should result in keycodes being added to HID reports.
### `beforeReportingState()`
The old version of this handler has been deprecated, but it will still be called
both before HID reports are sent and also once per cycle. It is likely that
these handlers will continue to function, but the code therein should be moved
either to the new `KeyEvent` version of `beforeReportingState()` and/or
`afterEachCycle()` (or `beforeEachCycle()`), depending on whether it needs to be
run only in response to input events or if it must execute every cycle,
respectively.

@ -42,6 +42,8 @@ If something goes wrong, the status bar turns orange and displays an error messa
![](images/arduino-setup/verify-failed.png) ![](images/arduino-setup/verify-failed.png)
If you see errors, refer to [Getting help](Getting-help) for troubleshooting tips and useful resources.
# Install the firmware # Install the firmware
If your keyboard has a programming interlock key, you'll need to hold it down now. On the Keyboardio Model 01, this is the `Prog` key. On the Keyboardio Atreus, this is the `Esc` key. If your keyboard has a programming interlock key, you'll need to hold it down now. On the Keyboardio Model 01, this is the `Prog` key. On the Keyboardio Atreus, this is the `Esc` key.
@ -49,8 +51,8 @@ If your keyboard has a programming interlock key, you'll need to hold it down no
Without releasing that key, click on the "right arrow" button in the sketch window menu bar. This starts the firmware installation process. Without releasing that key, click on the "right arrow" button in the sketch window menu bar. This starts the firmware installation process.
![](images/arduino-setup/press-prog.jpg) ![](images/arduino-setup/press-prog.png)
![](images/arduino-setup/press-prog-atreus.jpg) ![](images/arduino-setup/press-prog-atreus.png)
![](images/arduino-setup/upload-sketch.png) ![](images/arduino-setup/upload-sketch.png)

@ -21,11 +21,9 @@ Our style guide is based on the [Google C++ style guide][goog:c++-guide] which w
- [Header Files](#header-files) - [Header Files](#header-files)
- [Self-contained Headers](#self-contained-headers) - [Self-contained Headers](#self-contained-headers)
- [Header Guards](#header-guards) - [Header Guards](#header-guards)
- [Include What You Use](#include-what-you-use)
- [Forward Declarations](#forward-declarations) - [Forward Declarations](#forward-declarations)
- [Inline Functions](#inline-functions) - [Inline Functions](#inline-functions)
- [Organization of Includes](#organization-of-includes) - [Names and Order of Includes](#names-and-order-of-includes)
- [Top-level Arduino Library Headers](#top-level-arduino-library-headers)
- [Scoping](#scoping) - [Scoping](#scoping)
- [Namespaces](#namespaces) - [Namespaces](#namespaces)
- [Unnamed Namespaces and Static Variables](#unnamed-namespaces-and-static-variables) - [Unnamed Namespaces and Static Variables](#unnamed-namespaces-and-static-variables)
@ -132,7 +130,6 @@ Our style guide is based on the [Google C++ style guide][goog:c++-guide] which w
- [Vertical Whitespace](#vertical-whitespace) - [Vertical Whitespace](#vertical-whitespace)
- [Exceptions to the Rules](#exceptions-to-the-rules) - [Exceptions to the Rules](#exceptions-to-the-rules)
- [Existing Non-conformant Code](#existing-non-conformant-code) - [Existing Non-conformant Code](#existing-non-conformant-code)
- [Maintenance Tools](#maintenance-tools)
- [Parting Words](#parting-words) - [Parting Words](#parting-words)
## Background ## Background
@ -261,12 +258,6 @@ There are rare cases where a file designed to be included is not self-contained.
All header files should have `#pragma once` guards at the top to prevent multiple inclusion. All header files should have `#pragma once` guards at the top to prevent multiple inclusion.
### Include What You Use
If a source or header file refers to a symbol defined elsewhere, the file should directly include a header file which provides a declaration or definition of that symbol.
Do not rely on transitive inclusions. This allows maintainers to remove no-longer-needed `#include` statements from their headers without breaking clients code. This also applies to directly associated headers - `foo.cpp` should include `bar.h` if it uses a symbol defined there, even if `foo.h` (currently) includes `bar.h`.
### Forward Declarations ### Forward Declarations
> Avoid using forward declarations where possible. Just `#include` the headers you need. > Avoid using forward declarations where possible. Just `#include` the headers you need.
@ -320,80 +311,66 @@ Another useful rule of thumb: it's typically not cost effective to inline functi
It is important to know that functions are not always inlined even if they are declared as such; for example, virtual and recursive functions are not normally inlined. Usually recursive functions should not be inline. The main reason for making a virtual function inline is to place its definition in the class, either for convenience or to document its behavior, e.g., for accessors and mutators. It is important to know that functions are not always inlined even if they are declared as such; for example, virtual and recursive functions are not normally inlined. Usually recursive functions should not be inline. The main reason for making a virtual function inline is to place its definition in the class, either for convenience or to document its behavior, e.g., for accessors and mutators.
### Organization of Includes ### Names and Order of Includes
> Use standard order for readability and to avoid hidden dependencies: <!-- TODO: This section could be simplified, and clarified, I believe. -->
> - The header associated with this source file, if any
> - System headers and Arduino library headers (including other Kaleidoscope plugins, but not Kaleidoscope itself)
> - Kaleidoscope headers and headers for the individual plugin (other than the associated header above)
These three sections should be separated by single blank lines, and should be sorted alphabetically. > Use standard order for readability and to avoid hidden dependencies: Related header, Arduino libraries, other libraries' `.h`, your project's `.h`.
When including system headers and Arduino library headers (including Kaleidoscope plugins), use angle brackets to indicate that those sources are external. All libraries must have at least one header in their top-level `src/` directory, to be included without any path components. This is the way Arduino finds libraries, and a limitation we must adhere to. These headers should - in general - include any other headers they may need, so that the consumer of the library only has to include one header. The name of this header must be the same as the name of the library.
For headers inside the current library and for Kaleidoscope core headers, use double quotes and a full pathname (starting below the `src/` directory). This applies to the source file's associated header, as well; don't use a pathname relative to the source file's directory.
For the sake of clarity, the above sections can be further divided to make it clear where each included header file can be found, but this is probably not necessary in most cases, because the path name of a header usually indicates which library it is located in. The recommended naming is to prefix the library name with `Kaleidoscope-`.
For example, the includes in `Kaleidoscope-Something/src/kaleidoscope/Something.cpp` might look like this: If there is more than one header, they should be listed as descendants of the project's source directory without use of UNIX directory shortcuts `.` (the current directory) or `..` (the parent directory), and live in a `Kaleidoscope` subdirectory. For example, if we have a plugin named `Kaleidoscope-Something`, which has an additional header file other than `Kaleidoscope-Something.h`, it should be in `src/Kaleidoscope/Something-Other.h`, and be included as:
```c++ ```c++
#include "kaleidoscope/Something.h" #include "Kaleidoscope-Something.h"
#include "Kaleidoscope/Something-Other.h"
#include <Arduino.h>
#include <Kaleidoscope-Ranges.h>
#include <stdint.h>
#include "kaleidoscope/KeyAddr.h"
#include "kaleidoscope/KeyEvent.h"
#include "kaleidoscope/key_defs.h"
#include "kaleidoscope/plugin/something/utils.h"
``` ```
**Exception** Having more than one level of subdirectories is not recommended.
Sometimes, system-specific code needs conditional includes. Such code can put conditional includes after other includes. Of course, keep your system-specific code small and localized. Example: In `dir/foo.cpp` or `dir/foo_test.cpp`, whose main purpose is to implement or test the stuff in `dir2/foo2.h`, order your includes as follows:
```c++ 1. `dir2/foo2.h`
#if defined(ARDUINO_AVR_MODEL01) 2. Arduino libraries.
#include "kaleidoscope/Something-AVR-Model01.h" 3. Other libraries' `.h` files.
#endif 4. Your project's `.h` files.
#if defined(ARDUINO_AVR_SHORTCUT) With the preferred ordering, if `dir2/foo2.h` omits any necessary includes, the build of `dir/foo.cpp` or `dir/foo_test.cpp` will break. Thus, this rule ensures that build breakages show up first for the people working on these files, not for innocent people in other packages.
#include "kaleidoscope/Something-AVR-Shortcut.h"
#endif
```
### Top-level Arduinio Library Headers `dir/foo.cc` and `dir2/foo2.h` are usually in the same directory (e.g. `Kaleidoscope/Something_test.cpp` and `Kaleidoscope/Something.h`), but may sometimes be in different directories too.
All libraries must have at least one header in their top-level `src/` directory, to be included without any path components. This is the way Arduino finds libraries, and a limitation we must adhere to. These headers should - in general - include any other headers they may need, so that the consumer of the library only has to include one header. The name of this header must be the same as the name of the library. Within each section the includes should be ordered alphabetically.
The naming convention for Kaleidoscope plugins is to use the `Kaleidoscope-` prefix: e.g. `Kaleidoscope-Something`, which would have a top-level header named `Kaleidoscope-Something.h` in its `src/` directory. You should include all the headers that define the symbols you rely upon, except in the unusual case of [forward declarations](#forward-declarations). If you rely on symbols from `bar.h`, don't count on the fact that you included `foo.h` which (currently) includes `bar.h`: include `bar.h` yourself, unless `foo.h` explicitly demonstrates its intent to provide you the symbols of `bar.h`. However, any includes present in the related header do not need to be included again in the related `cc` (i.e., `foo.cc` can rely on `foo.h`'s includes).
In the case of Kaleidoscope plugin libraries, the number of source and header files tends to be very small (usually just one `*.cpp` file and its associated header, in addition to the library's top-level header). When one plugin depends on another, we therefore only include the top-level header of the dependency. For example, if `Kaleidoscope-OtherThing` depends on `Kaleidoscope-Something`, the file `kaleidoscope/plugin/OtherThing.h` will contain the line: For example, the includes in `Kaleidoscope-Something/src/Kaleidoscope/Something.cpp` might look like this:
```c++ ```c++
#include <Kaleidoscope-Something.h> #include "Kaleidoscope/Something.h"
```
…and `Kaleidoscope-Something.h` will look like this: #include "Arduino.h"
```c++ #include "Kaleidoscope-LEDControl.h"
#include "kaleidoscope/plugin/Something.h" #include "Kaleidoscope-Focus.h"
``` ```
This both makes it clearer where to find the included code, and allows the restructuring of that code without breaking the dependent library (assuming the symbols haven't changed as well). **Exception**
If a plugin library has symbols meant to be exported, and more than one header file in which those symbols are defined, all such header files should be included in the top-level header for the library. For example, if `Kaleidoscope-Something` defines types `kaleidoscope::plugin::Something` and `kaleidoscope::plugin::something::Helper`, both of which are meant to be accessible by `Kaleidoscope-OtherThing`, the top-level header `Kaleidoscope-Something.h` should look like this: Sometimes, system-specific code needs conditional includes. Such code can put conditional includes after other includes. Of course, keep your system-specific code small and localized. Example:
```c++ ```c++
#include "kaleidoscope/plugin/Something.h" #include "Kaleidoscope.h"
#include "kaleidoscope/plugin/something/Helper.h"
```
### Automated header includes checking #if defined(ARDUINO_AVR_MODEL01)
#include "Kaleidoscope/Something-AVR-Model01.h"
#endif
We have an automated wrapper for the `include-what-you-use` program from LLVM that processes most Kaleidoscope source files and updates their header includes to comply with the style guide. It requires at least version 0.18 of `include-what-you-use` in order to function properly (because earlier versions do not return a useful exit code, so determining if there was an error was difficult). It can be run by using the `make check-includes` target in the Kaleidoscope Makefile. #if defined(ARDUINO_AVR_SHORTCUT)
#include "Kaleidoscope/Something-AVR-Shortcut.h"
#endif
```
<!-- TODO: Finish converting the rest... --> <!-- TODO: Finish converting the rest... -->
@ -2403,7 +2380,7 @@ New code should not contain calls to deprecated interface points. Use the new in
Coding style and formatting are pretty arbitrary, but a project is much easier to follow if everyone uses the same style. Individuals may not agree with every aspect of the formatting rules, and some of the rules may take some getting used to, but it is important that all project contributors follow the style rules so that they can all read and understand everyone's code easily. Coding style and formatting are pretty arbitrary, but a project is much easier to follow if everyone uses the same style. Individuals may not agree with every aspect of the formatting rules, and some of the rules may take some getting used to, but it is important that all project contributors follow the style rules so that they can all read and understand everyone's code easily.
To help format code in compliance with this style guide, we use `clang-format`, which many editors can be configured to call automatically. There is also `make format` target available in the Kaleidoscope Makefile that will use `clang-format` to format all the core and plugin code. Our CI infrastructure checks to ensure that code has been formatted to these specifications. To help you format code correctly, we use "Artistic Style" 3.0. The `make astyle` target is available in the Kaleidoscope and plugin Makefiles. Our CI infrastructure enforces `astyle`'s decisions.
### Line Length ### Line Length
@ -3156,40 +3133,6 @@ The coding conventions described above are mandatory. However, like all good rul
If you find yourself modifying code that was written to specifications other than those presented by this guide, you may have to diverge from these rules in order to stay consistent with the local conventions in that code. If you are in doubt about how to do this, ask the original author or the person currently responsible for the code. Remember that *consistency* includes local consistency, too. If you find yourself modifying code that was written to specifications other than those presented by this guide, you may have to diverge from these rules in order to stay consistent with the local conventions in that code. If you are in doubt about how to do this, ask the original author or the person currently responsible for the code. Remember that *consistency* includes local consistency, too.
## Maintenance Tools
Kaleidoscope uses some automated tools to enforce compliance with this code style guide. Primarily, we use `clang-format` to format source files, `cpplint` to check for potential problems, and `include-what-you-use` to update header includes. These are invoked using python scripts that supply all of the necessary command-line parameters to both utilities. For convenience, there are also some shell scripts and makefile targets that further simplify the process of running these utilities properly.
### Code Formatting
We use `clang-format`(version 12 or higher) to automatically format Kaleidoscope source files. There is a top-level `.clang-format` config file that contains the settings that best match the style described in this guide. However, there are some files in the repository that are, for one reason or another, exempt from formatting in this way, so we use a wrapper script, `format-code.py`, instead of invoking it directly.
`format-code.py` takes a list of target filenames, either as command-line parameters or from standard input (if reading from a pipe, with each line treated as a filename), and formats those files. If given a directory target, it will recursively search that directory for source files, and format all of the ones it finds.
By default, `format-code.py` will first check for unstaged changes in the Kaleidoscope git working tree, and exit before formatting source files if any are found. This is meant to make it easier for developers to see the changes made by the formatter in isolation. It can be given the `--force` option to skip this check.
It also has a `--check` option, which will cause `format-code.py` to check for unstaged git working tree changes after running `clang-format` on the target source files, and return an error code if there are any, allowing us to automatically verify that code submitted complies with the formatting rules.
The easiest way to invoke the formatter on the whole repository is by running `make format` at the top level of the Kaleidoscope repository.
For automated checking of PRs in a CI tool, there is also `make check-code-style`.
### Linting
We use a copy of `cpplint.py` (with a modification that allows us to set the config file) to check for potential problems in the code. This can be invoked by running `make cpplint` at the top level of the Kaleidoscope repository.
### Header Includes
We use `include-what-you-use` (version 18 or higher) to automatically manage header includes in Kaleidoscope source files. Because of the peculiarities of Kaleidoscope's build system, we use a wrapper script, `iwyu.py`, instead of invoking it directly.
`iwyu.py` takes a list of target filenames, either as command-line parameters or from standard input (if reading from a pipe, with each line treated as a filename), and makes changes to the header includes. If given a directory target, it will recursively search that directory for source files, and run `include-what-you-use` on all of the ones it finds.
A number of files can't be processed this way, and are enumerated in `.iwyu_ignore`. Files in the `testing` directory (for the test simulator) require different include path items, so cannot be combined in the same call to `iwyu.py`.
The easiest way to invoke `iwyu.py` is by running `make check-includes`, which will check all files that differ between the git working tree and the current master branch of the main Kaleidoscope repository, update their headers, and return a non-zero exit code if there were any errors processing the file(s) or any changes made.
For automated checking of header includes in a CI tool, there is also `make check-all-includes`, that checks the whole repository, not just the current branch's changes.
## Parting Words ## Parting Words
Use common sense and *BE CONSISTENT*. Use common sense and *BE CONSISTENT*.

@ -1,25 +0,0 @@
# Docker
It's possible to use Docker to run Kaleidoscope's test suite.
## Running tests in Docker
```
# make docker-simulator-tests
```
## Cleaning out stale data in the Docker image:
```
# make docker-clean
```
## Removing the Kaleidoscope Docker image entirely:
```
# docker volume rm kaleidoscope-persist
# docker volume rm kaleidoscope-googletest-build
# docker volume rm kaleidoscope-build
# docker image rm kaleidoscope/docker
```

@ -4,82 +4,13 @@ This document is intended to name and describe the concepts, functions and data
It is, as yet, incredibly incomplete. It is, as yet, incredibly incomplete.
When describing an identifier of any kind from the codebase, it should be Entries should be included in dictionary order. When describing an identifier of any kind from the codebase, it should be
written using identical capitalization to its use in the code and surrounded by backticks: `identifierName` written using identical capitalization to its use in the code and surrounded by backticks: `identifierName`
## Firmware Terminology ## Firmware Terminology
These terms commonly arise when discussing the firmware. These terms commonly arise when discussing the firmware.
### Keyswitch
A single physical input, such as a keyswitch or other input like a knob or a slider
### Key number
An integer representing a Keyswitchs position in the “Physical Layout”. Represented in the code by the `KeyAddr` type.
### Physical Layout
A mapping of keyswitches to key numbers
### Key binding
A mapping from a key number to a behavior.
### `Key`
A representation of a specific behavior. Most often a representation of a specific entry in the USB HID Usage Tables.
### Keymap
A list of key bindings for all keyswitchess on the Physical Layout. Represented in the code by the `KeyMap` type.
### Keymaps
An ordered list of all the Keymaps installed on a keyboard.
### Layer
An entry in that ordered list of keymaps. Each layer has a unique id number that does not change. Layer numbers start at 0.
### Active Layer Stack
An ordered list of all the currently-active layers, in the order they should be evaluated when figuring out what a key does.
### Live keys
A representation of the current state of the keyboard's keys, where non-transparent entries indicate keys that are active (logically—usually, but not necessarily, physically held). Represented in the code by the `LiveKeys` type (and the `live_keys` object).
#### Active/inactive keys
In the `live_keys[]` array, an _active_ key usually corresponds to a keyswitch that is physically pressed. In the common case of HID Keyboard keys, an active key will result in one or more keycodes being inserted in any new HID report. In some cases, an key can be active when its physical keyswitch is not pressed (e.g. OneShot keys that have been tapped), and in other cases a key might be _inactive_ even though its keyswitch is pressed (e.g. a Qukeys key whose value has not yet been resolved). Inactive keys are represented in the `live_keys[]` array by the special value `Key_Inactive`.
#### Masked keys
In the `live_keys[]` array, a _masked_ key is one whose next key press (either physical or logical) will be ignored. A masked key is automatically unmasked the next time it toggles off. Masked keys are represented by the special value `Key_Masked`.
## Keyswitch state
### Pressed
The state of a keyswitch that has been actuated by the user or a routine acting on behalf of the user
### Unpressed
The state of a keyswitch that is not currently actuated
### Toggled on
The state of a keyswitch that was not pressed during the last scan cycle and is now pressed.
### Toggled off
The state of a keyswitch that was pressed during the last scan cycle and is no longer pressed.
### Cycle ### Cycle
The `loop` method in one's sketch file is the heart of the firmware. It runs - The `loop` method in one's sketch file is the heart of the firmware. It runs -

@ -0,0 +1,97 @@
# Release testing
Before a new release of Kaleidoscope, the following test process should be run through on all supported operating systems.
(As of August 2017, this whole thing really applies to Model01-Firmware, but we hope to generalize it to Kaleidoscope)
# Tested operating systems
* The latest stable Ubuntu Linux release running X11. (We _should_ eventually be testing both X11 and Wayland)
* The latest stable release of macOS
* An older Mac OS X release TBD. (There were major USB stack changes in 10.9 or so)
* Windows 10
* Windows 7
* The current release of ChromeOS
* A currentish android tablet that supports USB Host
* an iOS device (once we fix the usb connection issue to limit power draw)
# Test process
## Basic testing
1. Plug the keyboard in
1. Make sure the host OS doesn't throw an error
1. Make sure the LED in the top left doesn't glow red
1. Make sure the LED in the top-right corner of the left side breathes blue for ~10s
1. Bring up some sort of notepad app or text editor
## Basic testing, part 2
1. Test typing of shifted and unshifted letters and numbers with and without key repeat
1. Test typing of fn-shifted characters: []{}|\ with and without key repeat
1. Test that 'Any' key generates a random letter or number and that key repeat works
1. Test fn-hjkl to move the cursor
1. Test Fn-WASD to move the mouse
1. Test Fn-RFV for the three mouse buttons
1. Test Fn-BGTabEsc for mouse warp
1. Test that LeftFn+RightFn + hjkl move the cursor
1. Verify that leftfn+rightfn do not light up the numpad
## NKRO
1. Open the platform's native key event viewer
(If not available, visit https://www.microsoft.com/appliedsciences/KeyboardGhostingDemo.mspx in a browser)
1. Press as many keys as your fingers will let you
1. Verify that the keymap reports all the keys you're pressing
## Test media keys
1. Fn-Any: previous track
1. Fn-Y: next-track
1. Fn-Enter: play/pause
1. Fn-Butterfly: Windows 'menu' key
1. Fn-n: mute
1. Fn-m: volume down
1. Fn-,: volume up
## Test numlock
1. Tap "Num"
1. Verify that the numpad lights up red
1. Verify that the num key is breathing blue
1. Verify that numpad keys generate numbers
1. Tap the Num key
1. Verify that the numpad keys stop being lit up
1 Verify that 'jkl' don't generate numbers.
## Test LED Effects
1. Tap the LED key
1. Verify that there is a rainbow effect
1. Tap the LED key a few more times and verify that other LED effects show up
1. Verify that you can still type.
## Second connection
1. Unplug the keyboard
1. Plug the keyboard back in
1. Make sure you can still type
## Programming
1. If the OS has a way to show serial port devices, verify that the keyboard's serial port shows up.
1. If you can run stty, as you can on linux and macos, make sure you can tickle the serial port at 1200 bps.
Linux: stty -F /dev/ttyACM0 1200
Mac:
1. If you tickle the serial port without holding down the prog key, verify that the Prog key does not light up red
1. If you hold down the prog key before tickling the serial port, verify that the Prog key's LED lights up red.
1. Unplug the keyboard
1. While holding down prog, plug the keyboard in
1. Verify that the prog key is glowing red.
1. Unplug the keyboard
1. Plug the keyboard in
1. Verify that the prog key is not glowing red.
# If the current platform supports the Arduino IDE (Win/Lin/Mac)
1. use the Arduino IDE to reflash the current version of the software.
1. Verify that you can type a few keys
1. Verify that the LED key toggles between LED effects

@ -37,7 +37,9 @@ extensions = [
] ]
extensions.append('copy-examples') extensions.append('copy-examples')
extensions.append('copy-plugin-readmes')
examples_source = '../examples'
examples_dest = 'generated/examples'
# Setup the breathe extension # Setup the breathe extension
@ -100,8 +102,6 @@ html_theme_options = {
'style_external_links': False, 'style_external_links': False,
'vcs_pageview_mode': '', 'vcs_pageview_mode': '',
'style_nav_header_background': 'white', 'style_nav_header_background': 'white',
"display_github": False, # Add 'Edit on Github' link instead of 'View page source'
# Toc options # Toc options
'collapse_navigation': True, 'collapse_navigation': True,

@ -3,13 +3,11 @@
This is an annotated list of some of Kaleidoscope's most important core plugins. You may also want to consult the [automatically generated list of all plugins bundled with Kaleidoscope](../plugin_list). This is an annotated list of some of Kaleidoscope's most important core plugins. You may also want to consult the [automatically generated list of all plugins bundled with Kaleidoscope](../plugin_list).
You can find a list of third-party plugins not distributed as part of Kaleidoscope [on the forums][forum:plugin-list]. You can find a list of third-party plugins not distributed as part of Kaleidoscope at https://community.keyboard.io/c/programming/Discuss-Plugins-one-thread-per-plugin
[forum:plugin-list]: https://community.keyboard.io/c/programming/discuss-kaleidoscope-plugins-one-thread-per-plugin/
## EEPROM-Keymap ## EEPROM-Keymap
[EEPROM-Keymap Documentation](../plugins/Kaleidoscope-EEPROM-Keymap.md) [EEPROM-Keymap Documentation](../plugins/EEPROM-Keymap.md)
While keyboards usually ship with a keymap programmed in, to be able to change that keymap, without flashing new firmware, we need a way to place the keymap into a place we can update at run-time, and which persists across reboots. Fortunately, we have a bit of EEPROM on the keyboard, and can use it to store either the full keymap (and saving space in the firmware then), or store an overlay there. In the latter case, whenever there is a non-transparent key on the overlay, we will use that instead of the keyboard default. While keyboards usually ship with a keymap programmed in, to be able to change that keymap, without flashing new firmware, we need a way to place the keymap into a place we can update at run-time, and which persists across reboots. Fortunately, we have a bit of EEPROM on the keyboard, and can use it to store either the full keymap (and saving space in the firmware then), or store an overlay there. In the latter case, whenever there is a non-transparent key on the overlay, we will use that instead of the keyboard default.
@ -17,13 +15,21 @@ In short, this plugin allows us to change our keymaps, without having to compile
## Escape-OneShot ## Escape-OneShot
[Escape-OneShot Documentation](../plugins/Kaleidoscope-Escape-OneShot.md) [Escape-OneShot Documentation](../plugins/Escape-OneShot.md)
Turn the Esc key into a special key, that can cancel any active OneShot effect - or act as the normal Esc key if none are active. For those times when one accidentally presses a one-shot key, or change their minds. Turn the Esc key into a special key, that can cancel any active OneShot effect - or act as the normal Esc key if none are active. For those times when one accidentally presses a one-shot key, or change their minds.
## KeyLogger
[KeyLogger Documentation](../plugins/KeyLogger.md)
The KeyLogger plugin, as the name suggests, implements a key logger for the Kaleidoscope firmware. It logs the row and column of every key press and release, along with the event, and the layer number, in a format that is reasonably easy to parse, to the Serial interface.
**A word of warning**: Having a key logger is as dangerous as it sounds. Anyone who can read the serial events from the keyboard, will know exactly what keys you press, and when. Unless you know what you are doing, and can secure your keyboard, do not enable this plugin.
## Leader ## Leader
[Leader Documentation](../plugins/Kaleidoscope-Leader.md) [Leader Documentation](../plugins/Leader.md)
Leader keys are a kind of key where when they are tapped, all following keys are swallowed, until the plugin finds a matching sequence in the dictionary, it times out, or fails to find any possibilities. When a sequence is found, the corresponding action is executed, but the processing still continues. If any key is pressed that is not the continuation of the existing sequence, processing aborts, and the key is handled normally. Leader keys are a kind of key where when they are tapped, all following keys are swallowed, until the plugin finds a matching sequence in the dictionary, it times out, or fails to find any possibilities. When a sequence is found, the corresponding action is executed, but the processing still continues. If any key is pressed that is not the continuation of the existing sequence, processing aborts, and the key is handled normally.
@ -33,7 +39,7 @@ So we put ``LEAD u`` and ``LEAD u h e a r t`` in the dictionary only. The first
## Macros ## Macros
[Macros Documentation](../plugins/Kaleidoscope-Macros.md) [Macros Documentation](../plugins/Macros.md)
Macros are a standard feature on many keyboards and powered ones are no exceptions. Macros are a way to have a single key-press do a whole lot of things under the hood: conventionally, macros play back a key sequence, but with Kaleidoscope, there is much more we can do. Nevertheless, playing back a sequence of events is still the primary use of macros. Macros are a standard feature on many keyboards and powered ones are no exceptions. Macros are a way to have a single key-press do a whole lot of things under the hood: conventionally, macros play back a key sequence, but with Kaleidoscope, there is much more we can do. Nevertheless, playing back a sequence of events is still the primary use of macros.
@ -41,7 +47,7 @@ Playing back a sequence means that when we press a macro key, we can have it pla
## MagicCombo ## MagicCombo
[MagicCombo Documentation](../plugins/Kaleidoscope-MagicCombo.md) [MagicCombo Documentation](../plugins/MagicCombo.md)
The MagicCombo extension provides a way to perform custom actions when a particular set of keys are held down together. The functionality assigned to these keys are not changed, and the custom action triggers as long as all keys within the set are pressed. The order in which they were pressed do not matter. The MagicCombo extension provides a way to perform custom actions when a particular set of keys are held down together. The functionality assigned to these keys are not changed, and the custom action triggers as long as all keys within the set are pressed. The order in which they were pressed do not matter.
@ -49,7 +55,7 @@ This can be used to tie complex actions to key chords.
## OneShot ## OneShot
[OneShot Documentation](../plugins/Kaleidoscope-OneShot.md) [OneShot Documentation](../plugins/OneShot.md)
One-shots are a new kind of behaviour for your standard modifier and momentary layer keys: instead of having to hold them while pressing other keys, they can be tapped and released, and will remain active until any other key is pressed. In short, they turn ``Shift, A`` into ``Shift+A``, and ``Fn, 1`` to ``Fn+1``. The main advantage is that this allows us to place the modifiers and layer keys to positions that would otherwise be awkward when chording. Nevertheless, they still act as normal when held, that behaviour is not lost. One-shots are a new kind of behaviour for your standard modifier and momentary layer keys: instead of having to hold them while pressing other keys, they can be tapped and released, and will remain active until any other key is pressed. In short, they turn ``Shift, A`` into ``Shift+A``, and ``Fn, 1`` to ``Fn+1``. The main advantage is that this allows us to place the modifiers and layer keys to positions that would otherwise be awkward when chording. Nevertheless, they still act as normal when held, that behaviour is not lost.
@ -59,7 +65,7 @@ To make multi-modifier, or multi-layer shortcuts possible, one-shot keys remain
## Qukeys ## Qukeys
[Qukeys Documentation](../plugins/Kaleidoscope-Qukeys.md) [Qukeys Documentation](../plugins/Qukeys.md)
A Qukey is a key that has two possible values, usually a modifier and a printable character. The name is a play on the term "qubit" (short for "quantum bit") from quantum computing. The value produced depends on how long the key press lasts, and how it is used in combination with other keys (roughly speaking, whether the key is "tapped" or "held"). A Qukey is a key that has two possible values, usually a modifier and a printable character. The name is a play on the term "qubit" (short for "quantum bit") from quantum computing. The value produced depends on how long the key press lasts, and how it is used in combination with other keys (roughly speaking, whether the key is "tapped" or "held").
@ -71,7 +77,7 @@ It is also possible to use Qukeys like SpaceCadet (see below), by setting the pr
## ShapeShifter ## ShapeShifter
[ShapeShifter Documentation](../plugins/Kaleidoscope-ShapeShifter.md) [ShapeShifter Documentation](../plugins/ShapeShifter.md)
ShapeShifter is a plugin that makes it considerably easier to change what symbol is input when a key is pressed together with ``Shift``. If one wants to rearrange the symbols on the number row for example, without modifying the layout on the operating system side, this plugin is where one can turn to. ShapeShifter is a plugin that makes it considerably easier to change what symbol is input when a key is pressed together with ``Shift``. If one wants to rearrange the symbols on the number row for example, without modifying the layout on the operating system side, this plugin is where one can turn to.
@ -79,7 +85,7 @@ What it does, is very simple: if any key in its dictionary is found pressed whil
## SpaceCadet ## SpaceCadet
[SpaceCadet Documentation](../plugins/Kaleidoscope-SpaceCadet.md) [SpaceCadet Documentation](../plugins/SpaceCadet.md)
Space Cadet is a way to make it more convenient to input parens - those ``(`` and ``)`` things -, symbols that a lot of programming languages use frequently. If you are working with Lisp, you are using these all the time. Space Cadet is a way to make it more convenient to input parens - those ``(`` and ``)`` things -, symbols that a lot of programming languages use frequently. If you are working with Lisp, you are using these all the time.
@ -91,7 +97,7 @@ After getting used to the Space Cadet style of typing, you may wish to enable th
## TapDance ## TapDance
[TapDance Documentation](../plugins/Kaleidoscope-TapDance.md) [TapDance Documentation](../plugins/TapDance.md)
Tap-dance keys are general purpose, multi-use keys, which trigger a different action based on the number of times they were tapped in sequence. As an example to make this clearer, one can have a key that inputs ``A`` when tapped once, inputs ``B`` when tapped twice, and lights up the keyboard in Christmas colors when tapped a third time. Tap-dance keys are general purpose, multi-use keys, which trigger a different action based on the number of times they were tapped in sequence. As an example to make this clearer, one can have a key that inputs ``A`` when tapped once, inputs ``B`` when tapped twice, and lights up the keyboard in Christmas colors when tapped a third time.
@ -113,6 +119,7 @@ There is one additional value the tapDanceAction parameter can ``take: kaleidosc
## TopsyTurvy ## TopsyTurvy
[TopsyTurvy Documentation](../plugins/Kaleidoscope-TopsyTurvy.md) [TopsyTurvy Documentation](../plugins/TopsyTurvy.md)
TopsyTurvy is a plugin that inverts the behaviour of the Shift key for some selected keys. That is, if configured so, it will input ``!`` when pressing the ``1`` key without ``Shift``, but with the modifier pressed, it will input the original ``1`` symbol. TopsyTurvy is a plugin that inverts the behaviour of the Shift key for some selected keys. That is, if configured so, it will input ``!`` when pressing the ``1`` key without ``Shift``, but with the modifier pressed, it will input the original ``1`` symbol.

@ -4,16 +4,16 @@ Eventually there should be a helpful table here with good definitions for the co
- Most of the common keyboard key codes are here: - Most of the common keyboard key codes are here:
[key_defs/keyboard.h](https://github.com/keyboardio/Kaleidoscope/blob/master/src/kaleidoscope/key_defs/keyboard.h) [key_defs_keyboard.h](https://github.com/keyboardio/Kaleidoscope/blob/master/src/kaleidoscope/key_defs_keyboard.h)
- Key codes for system tasks like shutting down, switching windows, and moving through menus are here: - Key codes for system tasks like shutting down, switching windows, and moving through menus are here:
[key_defs/sysctl.h](https://github.com/keyboardio/Kaleidoscope/blob/master/src/kaleidoscope/key_defs/sysctl.h) [key_defs_sysctl.h](https://github.com/keyboardio/Kaleidoscope/blob/master/src/kaleidoscope/key_defs_sysctl.h)
- A wide range of key codes for controlling consumer electronics, most of which are probably not relevant, are in this file: - A wide range of key codes for controlling consumer electronics, most of which are probably not relevant, are in this file:
[key_defs/consumerctl.h](https://github.com/keyboardio/Kaleidoscope/blob/master/src/kaleidoscope/key_defs/consumerctl.h) [key_defs_consumerctl.h](https://github.com/keyboardio/Kaleidoscope/blob/master/src/kaleidoscope/key_defs_consumerctl.h)
## In-keymap chorded keys ## In-keymap chorded keys
In addition, the keys in `key_defs/keyboard.h` can be augmented with modifier macros: `LCTRL()`, `LSHIFT()`, `LALT()`, `LGUI()` and `RALT()` to add chorded keys to your keymap. For example `LCTRL(LALT(Key_Delete))` can be used to add control-alt-delete as a single key to your keymap, should you wish. The innermost bracket must be of the standard format as taken from the above key definitions, and all other modifiers must be from the aforementioned list, and in that format. This does allow you to create single keys for multiple modifiers, e.g. `LCTRL(LALT(LSHIFT(Key_LeftGui)))`, when held, would have the effect of all left modifiers at once. These modifier macros only work for standard keys! When applied to any key provided by a plugin, they will have no effect. In addition, the keys in `key_defs_keyboard.h` can be augmented with modifier macros: `LCTRL()`, `LSHIFT()`, `LALT()`, `LGUI()` and `RALT()` to add chorded keys to your keymap. For example `LCTRL(LALT(Key_Delete))` can be used to add control-alt-delete as a single key to your keymap, should you wish. The innermost bracket must be of the standard format as taken from the above key definitions, and all other modifiers must be from the aforementioned list, and in that format. This does allow you to create single keys for multiple modifiers, e.g. `LCTRL(LALT(LSHIFT(Key_LeftGui)))`, when held, would have the effect of all left modifiers at once. These modifier macros only work for standard keys! When applied to any key provided by a plugin, they will have no effect.

@ -1,458 +0,0 @@
# How to write a Kaleidoscope plugin
This is a brief guide intended for those who want to write custom Kaleidoscope plugins. It covers basic things you'll need to know about how Kaleidoscope calls plugin event handlers, and how it will respond to actions taken by those plugins.
## What can a plugin do?
There are many things that Kaleidoscope plugins are capable of, from LED effects, serial communication with the host, altering HID reports, and interacting with other plugins. It's useful to break these capabilities down into some broad categories, based on the types of input a plugin can respond to.
- Key events (key switches toggling on and off)
- Focus commands (sent to the keyboard from software on the host via the serial port)
- LED updates
- Keymap layer changes
- Timers
## An example plugin
To make a Kaleidoscope plugin, we create a subclass of the `kaleidoscope::Plugin` class, usually in the `kaleidoscope::plugin` namespace:
```c++
namespace kaleidoscope {
namespace plugin {
class MyPlugin : public Plugin {};
} // namespace kaleidoscope
} // namespace plugin
```
This code can be placed in a separate C++ source file, but it's simplest to just define it right in the sketch's \*.ino file for now.
By convention, we create a singleton object named like the plugin's class in the global namespace. This is typical of Arduino code.
```c++
kaleidoscope::plugin::MyPlugin MyPlugin;
```
Next, in order to connect that plugin to the Kaleidoscope event handler system, we need to register it in the call to the preprocessor macro `KALEIDOSCOPE_INIT_PLUGINS()` in the sketch:
```c++
KALEIDOSCOPE_INIT_PLUGINS(MyPlugin, OtherPlugin);
```
To make our plugin do anything useful, we need to add [[event-handler-hooks]] to it. This is how Kaleidoscope delivers input events to its registered plugins. Here's an example:
```c++
class MyPlugin : public Plugin {
public:
EventHandlerResult onKeyEvent(KeyEvent &event);
};
```
This will result in `MyPlugin.onKeyEvent()` being called (along with other plugins' `onKeyEvent()` methods) when Kaleidoscope detects a key state change. This function returns one of three `EventHandlerResult` values:
- `EventHandlerResult::OK` indicates that Kaleidoscope should proceed on to the event handler for the next plugin in the chain.
- `EventHandlerResult::ABORT` indicates that Kaleidoscope should stop processing immediately, and treat the event as if it didn't happen.
- `EventHandlerResult::EVENT_CONSUMED` stops event processing like `ABORT`, but records that the key is being held.
The `onKeyEvent()` method takes one argument: a reference to a `KeyEvent` object, which is a simple container for these essential bits of information:
- `event.addr` — the physical location of the keyswitch, if any
- `event.state` — a bitfield containing information on the current and previous state of the keyswitch (from which we can find out if it just toggled on or toggled off)
- `event.key` — a 16-bit `Key` value containing the contents looked up from the sketch's current keymap (if the key just toggled on) or the current live value of the key (if the key just toggled off)
Because the `KeyEvent` parameter is passed by (mutable) reference, our plugin's `onKeyEvent()` method can alter the components of the event, causing subsequent plugins (and, eventually, Kaleidoscope itself) to treat it as if it was a different event. In practice, except in very rare cases, the only member of a `KeyEvent` that a plugin should alter is `event.key`. Here's a very simple `onKeyEvent()` handler that changes all `X` keys into `Y` keys:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_X)
event.key = Key_Y;
return EventHandlerResult::OK;
}
```
### The difference between `ABORT` & `EVENT_CONSUMED`
Here's a plugin that will suppress all `X` key events:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_X)
return EventHandlerResult::ABORT;
return EventHandlerResult::OK;
}
```
Here's an almost identical plugin that has an odd failure mode:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_X)
return EventHandlerResult::EVENT_CONSUMED;
return EventHandlerResult::OK;
}
```
In this case, when an `X` key is pressed, no Keyboard HID report will be generated and sent to the host, but the key will still be recorded by Kaleidoscope as "live". If we hold that key down and press a `Y` key, we will suddenly see both `x` _and_ `y` in the output on the host. This is because returning `ABORT` suppresses the key event entirely, as if it never happened, whereas `EVENT_CONSUMED` signals to Kaleidoscope that the key should still become "live", but that no further processing is necessary. In this case, since we want to suppress all `X` keys entirely, we should return `ABORT`.
### A complete in-sketch plugin
Here's an example of a very simple plugin, defined as it would be in a firmware sketch (e.g. a `*.ino` file):
```c++
namespace kaleidoscope {
namespace plugin {
class KillX : public Plugin {
public:
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_X)
return EventHandlerResult::ABORT;
return EventHandlerResult::OK;
}
};
} // namespace kaleidoscope
} // namespace plugin
kaleidoscope::plugin::KillX;
```
On its own, this plugin won't have any effect unless we register it later in the sketch like this:
```c++
KALEIDOSCOPE_INIT_PLUGINS(KillX);
```
Note: `KALEIDOSCOPE_INIT_PLUGINS()` should only appear once in a sketch, with a list of all the plugins to be registered.
## Plugin registration order
Obviously, the `KillX` plugin isn't very useful. But more important, it's got a potential problem. Suppose we had another plugin defined, like so:
```c++
namespace kaleidoscope {
namespace plugin {
class YtoX : public Plugin {
public:
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_Y)
event.key = Key_X;
return EventHandlerResult::OK;
}
};
} // namespace kaleidoscope
} // namespace plugin
kaleidoscope::plugin::YtoX;
```
`YtoX` changes any `Y` key to an `X` key. These two plugins both work fine on their own, but when we put them together, we get some undesirable behavior. Let's try it this way first:
```c++
KALEIDOSCOPE_INIT_PLUGINS(YtoX, KillX);
```
This registers both plugins' event handlers with Kaleidoscope, in order, so for each `KeyEvent` generated in response to a keyswitch toggling on or off, `YtoX.onKeyEvent(event)` will get called first, then `KillX.onKeyEvent(event)` will get called.
If we press `X`, the `YtoX` plugin will effectively ignore the event, allowing it to pass through to `KillX`, which will abort the event.
If we press `Y`, `YtoX.onKeyEvent()` will change `event.key` from `Key_Y` to `Key_X`. Then, `KillX.onKeyEvent()` will abort the event. As a result, both `X` and `Y` keys will be suppressed by the combination of the two plugins.
---
Now, let's try the same two plugins in the other order:
```c++
KALEIDOSCOPE_INIT_PLUGINS(KillX, YtoX);
```
If we press `X`, its keypress event will get aborted by `KillX.onKeyEvent()`, and that key will not become live, so when it gets released, the event generated won't have the value `Key_X`, but will instead by `Key_Inactive`, which will not result in anything happening, either from the plugins or from Kaleidoscope itself.
Things get interesting if we press and release `Y`, though. First, `KillX.onKeyEvent()` will simply return `OK`, allowing `YtoX.onKeyEvent()` to change `event.key` from `Key_Y` to `Key_X`, causing that `Key_X` to become live, and sending its keycode to the host in the Keyboard USB HID report. That's all as expected, but then we release the key, and that's were it goes wrong.
`KillX.onKeyEvent()` doesn't distinguish between presses and releases. When a key toggles off, rather than looking up that key's value in the keymap, Kaleidoscope takes it from the live keys array. That means that `event.key` will be `Key_X` when `KillX.onKeyEvent()` is called, which will result in that event being aborted. And when an event is aborted, the key's entry in the live keys array doesn't get updated, so Kaleidoscope will treat it as if the key is still held after release. Thus, far from preventing the keycode for `X` getting to the host, it keeps that key pressed forever! The `X` key becomes "stuck on" because the plugin suppresses both key _presses_ and key _releases_.
### Differentiating between press and release events
There is a solution to this problem, which is to have `KillX` suppress `Key_X` toggle-on events, but not toggle-off events:
```c++
EventHandlerResult KillX::onKeyEvent(KeyEvent &event) {
if (event.key == Key_X && keyToggledOn(event.state))
return EventHandlerResult::ABORT;
return EventHandlerResult::OK;
}
```
Kaleidoscope provides `keyToggledOn()` and `keyToggledOff()` functions that operate on the `event.state` bitfield, allowing plugins to differentiate between the two different event states. With this new version of the `KillX` plugin, it won't keep an `X` key live, but it will stop one from _becoming_ live.
Our two plugins still yield results that depend on registration order in `KALEIDOSCOPE_INIT_PLUGINS()`, but the bug where the `X` key becomes "stuck on" is gone.
It is very common for plugins to only act on key toggle-on events, or to respond differently to toggle-on and toggle-off events.
## Timers
Another thing that many plugins need to do is handle timeouts. For example, the OneShot plugin keeps certain keys live for a period of time after those keys are released. Kaleidoscope provides some infrastructure to help us keep track of time, starting with the `afterEachCycle()` "event" handler function.
The `onKeyEvent()` handlers only get called in response to keyswitches toggling on and off (or as a result of plugins calling `Runtime.handleKeyEvent()`). If the user isn't actively typing for a period, its `onKeyEvent()` handler won't get called at all, so it's not very useful to check timers in that function. Instead, if we need to know if a timer has expired, we need to do it in a function that gets called regularly, regardless of input. The `afterEachCycle()` handler gets called once per cycle, guaranteed.
This is what an `afterEachCycle()` handler looks like:
```c++
EventHandlerResult afterEachCycle() {
return EventHandlerResult::OK;
}
```
It returns an `EventHandlerResult`, like other event handlers, but this one's return value is ignored by Kaleidoscope; returning `ABORT` or `EVENT_CONSUMED` has no effect on other plugins.
In addition to this, we need a way to keep track of time. For this, Kaleidoscope provides the function `Runtime.millisAtCycleStart()`, which returns an unsigned integer representing the number of milliseconds that have elapsed since the keyboard started. It's a 32-bit integer, so it won't overflow until about one month has elapsed, but we usually want to use as few bytes of RAM as possible on our MCU, so most timers store only as many bytes as needed, usually a `uint16_t`, which overflows after about one minute, or even a `uint8_t`, which is good for up to a quarter of a second.
We need to use an integer type that's at least as big as the longest timeout we expect to be used, but integer overflow can still give us the wrong answer if we check it by naïvely comparing the current time to the time at expiration, so Kaleidoscope provides a timeout-checking service that's handles the integer overflow properly: `Runtime.hasTimeExpired(start_time, timeout)`. To use it, your plugin should store a timestamp when the timer begins, using `Runtime.millisAtCycleStart()` (usually set in response to an event in `onKeyEvent()`). Then, in its `afterEachCycle()` call `hasTimeExpired()`:
```c++
namespace kaleidoscope {
namespace plugin {
class MyPlugin : public Plugin {
public:
constexpr uint16_t timeout = 500;
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_X && keyToggledOn(event.state)) {
start_time_ = Runtime.millisAtCycleStart();
timer_running_ = true;
}
return EventHandlerResult::OK;
}
EventHandlerResult afterEachCycle() {
if (Runtime.hasTimeExpired(start_time_, timeout)) {
timer_running_ = false;
// do something...
}
return EventHandlerResult::OK;
}
private:
bool timer_running_ = false;
uint16_t start_time_;
};
} // namespace kaleidoscope
} // namespace plugin
kaleidoscope::plugin::MyPlugin;
```
In the above example, the private member variable `start_time_` and the constant `timeout` are the same type of unsigned integer (`uint16_t`), and we've used the additional boolean `timer_running_` to keep from checking for timeouts when `start_time_` isn't valid. This plugin does something (unspecified) 500 milliseconds after a `Key_X` toggles on.
## Creating additional events
Another thing we might want a plugin to do is generate "extra" events that don't correspond to physical state changes. An example of this is the Macros plugin, which might turn a single keypress into a series of HID reports sent to the host. Let's build a simple plugin to illustrate how this is done, by making a key type a string of characters, rather than a single one.
For the sake of simplicity, let's make the key `H` result in the string `Hi!` being typed (from the point of view of the host computer). To do this, we'll make a plugin with an `onKeyEvent()` handler (because we want it to respond to a particular keypress event), which will call `Runtime.handleKeyEvent()` to generate new events sent to the host.
The first thing we need to understand to do this is how to use the `KeyEvent()` constructor to create a new `KeyEvent` object. For example:
```c++
KeyEvent event = KeyEvent(KeyAddr::none(), IS_PRESSED, Key_H);
```
This creates a `KeyEvent` where `event.addr` is an invalid address that doesn't correspond to a physical keyswitch, `event.state` has only the `IS_PRESSED` bit set, but not `WAS_PRESSED`, which corresponds to a key toggle-on event, and `event.key` is set to `Key_H`.
We can then cause Kaleidoscope to process this event, including calling plugin handlers, by calling `Runtime.handleKeyEvent(event)`:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_H && keyToggledOn(event.state)) {
// Create and send the `H` (shift + h)
KeyEvent new_event = KeyEvent(KeyAddr::none(), IS_PRESSED, LSHIFT(Key_H));
Runtime.handleKeyEvent(new_event);
// Change the key value and send `i`
new_event.key = Key_I;
Runtime.handleKeyEvent(new_event);
// Change the key value and send `!` (shift + 1)
new_event.key = LSHIFT(Key_1);
Runtime.handleKeyEvent(new_event);
return EventHandlerResult::ABORT;
}
return EventHandlerResult::OK;
}
```
A few shortcuts were taken with this plugin that are worth pointing out. First, you may have noticed that we didn't send any key _release_ events, just three presses. This works, but there's a small chance that it could cause problems for some plugin that's trying to match key presses and releases. To be nice (or pedantic, if you will), we could also send the matching release events, but this is probably not necessary in this case, because we've used an invalid key address (`KeyAddr::none()`) for these generated events. This means that Kaleidoscope will not be recording these events as held keys. If we had used valid key addresses (corresponding to physical keyswitches) instead, it would be more important to send matching release events to keep keys from getting "stuck" on. For example, we could just use the address of the `H` key that was pressed:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_H && keyToggledOn(event.state)) {
KeyEvent new_event = KeyEvent(event.addr, IS_PRESSED, LSHIFT(Key_H));
Runtime.handleKeyEvent(new_event);
new_event.key = Key_I;
Runtime.handleKeyEvent(new_event);
new_event.key = LSHIFT(Key_1);
Runtime.handleKeyEvent(new_event);
return EventHandlerResult::ABORT;
}
return EventHandlerResult::OK;
}
```
This new version has the curious property that if the `H` key is held long enough, it will result in repeating `!!!!` characters on the host, until the key is released, which will clear it. In fact, instead of creating a whole new `KeyEvent` object, we could further simplify this plugin by simply modifying the `event` object that we already have, instead:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_H && keyToggledOn(event.state)) {
event.key = LSHIFT(Key_H);
Runtime.handleKeyEvent(event);
event.key = Key_I;
Runtime.handleKeyEvent(event);
event.key = LSHIFT(Key_1);
}
return EventHandlerResult::OK;
}
```
Note that, with this version, we've only sent two extra events, then changed the `event.key` value, and returned `OK` instead of `ABORT`. This is basically the same as the above pluging that turned `Y` into `X`, but with two extra events sent first.
As one extra precaution, it would be wise to mark the generated event(s) as "injected" to let other plugins know that these events should be ignored. This is a convention that is used by many existing Kaleidoscope plugins. We do this by setting the `INJECTED` bit in the `event.state` variable:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_H && keyToggledOn(event.state)) {
event.state |= INJECTED;
event.key = LSHIFT(Key_H);
Runtime.handleKeyEvent(event);
event.key = Key_I;
Runtime.handleKeyEvent(event);
event.key = LSHIFT(Key_1);
}
return EventHandlerResult::OK;
}
```
If we wanted to be especially careful, we could also add the corresponding release events:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_H && keyToggledOn(event.state)) {
event.key = LSHIFT(Key_H);
event.state = INJECTED | IS_PRESSED;
Runtime.handleKeyEvent(event);
event.state = INJECTED | WAS_PRESSED;
Runtime.handleKeyEvent(event);
event.key = Key_I;
event.state = INJECTED | IS_PRESSED;
Runtime.handleKeyEvent(event);
event.state = INJECTED | WAS_PRESSED;
Runtime.handleKeyEvent(event);
event.key = LSHIFT(Key_1);
event.state = INJECTED | IS_PRESSED;
}
return EventHandlerResult::OK;
}
```
### Avoiding infinite loops
One very important consideration for any plugin that calls `Runtime.handleKeyEvent()` from an `onKeyEvent()` handler is recursion. `Runtime.handleKeyEvent()` will call all plugins' `onKeyEvent()` handlers, including the one that generated the event. Therefore, we need to take some measures to short-circuit the resulting recursive call so that our plugin doesn't cause an infinite loop.
Suppose the example plugin above was changed to type the string `hi!` instead of `Hi!`. When sending the first generated event, with `event.key` set to `Key_H`, our plugin would recognize that event as one that should be acted on, and make another call to `Runtime.handleKeyEvent()`, which would again call `MyPlugin.onKeyEvent()`, and so on until the MCU ran out of memory on the stack.
The simplest mechanism used by many plugins that mark their generated events "injected" is to simply ignore all generated events:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if ((event.state & INJECTED) != 0)
return EventHandlerResult::OK;
if (event.key == Key_H && keyToggledOn(event.state)) {
event.state |= INJECTED;
event.key = LSHIFT(Key_H);
Runtime.handleKeyEvent(event);
event.key = Key_I;
Runtime.handleKeyEvent(event);
event.key = LSHIFT(Key_1);
}
return EventHandlerResult::OK;
}
```
There are other techniques to avoid inifinite loops, employed by plugins whose injected events should be processed by other plugins, but since most of those will be using the `onKeyswitchEvent()` handler instead of `onKeyEvent()`, we'll cover that later in this guide.
## Physical keyswitch events
Most plugins that respond to key events can do their work using the `onKeyEvent()` handler, but in some cases, it's necessary to use the `onKeyswitchEvent()` handler instead. These event handlers are strictly intended for physical keyswitch events, and plugins that implement the `onKeyswitchEvent()` handler must abide by certain rules in order to work well with each other. As a result, such a plugin is a bit more complex, but there are helper mechanisms to make things easier:
```c++
#include "kaleidoscope/KeyEventTracker.h"
namespace kaleidoscope {
namespace plugin {
class MyKeyswitchPlugin : public Plugin {
public:
EventHandlerResult onKeyswitchEvent(KeyEvent &event) {
if (event_tracker_.shouldIgnore(event))
return EventHandlerResult::OK;
// Plugin logic goes here...
return EventHandlerResult::OK;
}
private:
KeyEventTracker event_tracker_;
};
} // namespace kaleidoscope
} // namespace plugin
kaleidoscope::plugin::MyKeyswitchPlugin;
```
We've just added a `KeyEventTracker` object to our plugin, and made the first line of its `onKeyswitchEvent()` handler call that event tracker's `shouldIgnore()` method, returning `OK` if it returns `true` (thereby ignoring the event). Every plugin that implements `onKeyswitchEvent()` should follow this template to avoid plugin interaction bugs, including possible infinite loops.
The main reason for this event tracker mechanism is that plugins with `onKeyswitchEvent()` handlers often delay events because some aspect of those events (usually `event.key`) needs to be determined by subsequent events or timeouts. To do this, event information is stored, and the event is later regenerated by the plugin, which calls `Runtime.handleKeyswitchEvent()` so that the other `onKeyswitchEvent()` handlers can process it.
We need to prevent infinite loops, but simply marking the regenerated event `INJECTED` is no good, because it would prevent the other plugins from acting on it, so we instead keep track of a monotonically increasing event id number and use the `KeyEventTracker` helper class to ignore events that our plugin has already recieved, so that when the plugin regenerates an event with the same event id, it (and all the plugins before it) can ignore that event, but the subsequent plugins, which haven't seen that event yet, will recongize it as new and process the event accordingly.
### Regenerating stored events
When a plugin that implements `onKeyswitchEvent()` regenerates a stored event later so that it can be processed by the next plugin in the chain, it must use the correct event id value (the same one used by the original event). This is an object of type `EventId`, and is retrieved by calling `event.id()` (unlike the other properties of a `KeyEvent` object the event id is not directly accessible).
```c++
KeyEventId stored_id = event.id();
```
When reconstructing an event to allow it to proceed, we then use the four-argument version of the `KeyEvent` constructor:
```c++
KeyEvent event = KeyEvent(addr, state, key, stored_id);
```
In the above, `addr` and `state` are usually also the same as the original event's values, and `key` is most often the thing that changes. If your plugin wants a keymap lookup to take place, the value `Key_Undefined` can be used instead of explicitly doing the lookup itself.
## Controlling LEDs
## HID reports
## Layer changes

@ -1,5 +1,3 @@
# Testing Kaleidoscope
This is not yet proper documentation about running or writing tests, just some rough notes. This is not yet proper documentation about running or writing tests, just some rough notes.
Kaleidoscope includes a simulator that can pretend (to a certain extent) to be a keyboard for the purpose of testing. Kaleidoscope includes a simulator that can pretend (to a certain extent) to be a keyboard for the purpose of testing.

@ -9,5 +9,5 @@ All example sketches
:maxdepth: 8 :maxdepth: 8
:glob: :glob:
examples/** generated/examples/**/*.ino

@ -7,7 +7,7 @@ teensy2 variant too.
To select which one to build for, you can either use the Arduino IDE, and select To select which one to build for, you can either use the Arduino IDE, and select
the appropriate Pinout and CPU, or, if using `kaleidoscope-builder`, you can add the appropriate Pinout and CPU, or, if using `kaleidoscope-builder`, you can add
a `LOCAL_CFLAGS` setting to your Makefile a `LOCAL_CFLAGS` setting to `.kaleidoscope-builder.conf`.
For the post-2016 variant (the default, also used by the handwired variant from For the post-2016 variant (the default, also used by the handwired variant from
FalbaTech), if you want to explicitly select it, add FalbaTech), if you want to explicitly select it, add

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 KiB

After

Width:  |  Height:  |  Size: 626 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

After

Width:  |  Height:  |  Size: 624 KiB

@ -9,9 +9,9 @@ Kaleidoscope
Flexible firmware for computer keyboards. Flexible firmware for computer keyboards.
This package contains the "core" of Kaleidoscope and a number of `example firmware "Sketches" <https://github.com/keyboardio/Kaleidoscope/tree/master/examples>`_ This package contains the "core" of Kaleidoscope and a number of [example firmware "Sketches"](https://github.com/keyboardio/Kaleidoscope/tree/master/examples).
If you're just getting started with the Keyboardio Model 01, the `introductory docs are here <https://github.com/keyboardio/Kaleidoscope/wiki/Keyboardio-Model-01-Introduction>`_ and the source for the basic firmware package is here: https://github.com/keyboardio/Model01-Firmware. It's probably a good idea to start there, learn how to modify your keymap and maybe turn some modules on or off, and then come back to the full repository when you have more complex changes in mind. If you're just getting started with the Keyboardio Model 01, the [introductory docs are here](https://github.com/keyboardio/Kaleidoscope/wiki/Keyboardio-Model-01-Introduction) and the source for the basic firmware package is here: https://github.com/keyboardio/Model01-Firmware. It's probably a good idea to start there, learn how to modify your keymap and maybe turn some modules on or off, and then come back to the full repository when you have more complex changes in mind.
.. toctree:: .. toctree::
:caption: Installation and setup :caption: Installation and setup
@ -68,10 +68,10 @@ For developers
drivers/** drivers/**
.. toctree:: .. toctree::
:caption: Testing :caption: Development tips
:glob: :glob:
testing/** development/**
.. toctree:: .. toctree::
@ -90,8 +90,8 @@ For developers
.. toctree:: .. toctree::
:caption: Docs that don't work yet
:maxdepth: 2 :maxdepth: 2
:caption: Examples
examples examples
about about

@ -0,0 +1,69 @@
# Colormap
The `Colormap` extension provides an easier way to set up a different - static -
color map per-layer. This means that we can set up a map of colors for each key,
on a per-layer basis, and whenever a layer becomes active, the color map for
that layer is applied. Colors are picked from a 16-color palette, provided by
the [LED-Palette-Theme][plugin:l-p-t] plugin. The color map is stored in
`EEPROM`, and can be easily changed via the [FocusSerial][plugin:focusserial]
plugin, which also provides palette editing capabilities.
[plugin:focusserial]: FocusSerial.md
[plugin:l-p-t]: LED-Palette-Theme.md
## Using the extension
To use the extension, include the header, tell it the number of layers you have,
register the `Focus` hooks, and it will do the rest.
```c++
#include <Kaleidoscope.h>
#include <Kaleidoscope-EEPROM-Settings.h>
#include <Kaleidoscope-Colormap.h>
#include <Kaleidoscope-FocusSerial.h>
#include <Kaleidoscope-LED-Palette-Theme.h>
KALEIDOSCOPE_INIT_PLUGINS(EEPROMSettings,
LEDPaletteTheme,
ColormapEffect,
Focus);
void setup(void) {
Kaleidoscope.setup();
ColormapEffect.max_layers(1);
}
```
## Plugin methods
The extension provides an `ColormapEffect` singleton object, with a single method:
### `.max_layers(max)`
> Tells the extension to reserve space in EEPROM for up to `max` layers. Can
> only be called once, any subsequent call will be a no-op.
## Focus commands
### `colormap.map`
> Without arguments, prints the color map: palette indexes for all layers.
>
> With arguments, updates the color map with new indexes. One does not need to
> give the full map, the plugin will process as many arguments as available, and
> ignore anything past the last key on the last layer (as set by the
> `.max_layers()` method).
## Dependencies
* [Kaleidoscope-EEPROM-Settings](EEPROM-Settings.md)
* [Kaleidoscope-FocusSerial](FocusSerial.md)
* [Kaleidoscope-LED-Palette-Theme](LED-Palette-Theme.md)
## Further reading
Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin.
[plugin:example]: ../../examples/LEDs/Colormap/Colormap.ino

@ -34,7 +34,7 @@ void cycleAction(Key previous_key, uint8_t cycle_count) {
KALEIDOSCOPE_INIT_PLUGINS(Cycle); KALEIDOSCOPE_INIT_PLUGINS(Cycle);
void setup() { void setup(void) {
Kaleidoscope.setup(); Kaleidoscope.setup();
} }
``` ```
@ -89,11 +89,11 @@ method explained below.
## Dependencies ## Dependencies
* [Kaleidoscope-Ranges](Kaleidoscope-Ranges.md) * [Kaleidoscope-Ranges](Ranges.md)
## Further reading ## Further reading
Starting from the [example][plugin:example] is the recommended way of getting Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin. started with the plugin.
[plugin:example]: /examples/Keystrokes/Cycle/Cycle.ino [plugin:example]: ../../examples/Keystrokes/Cycle/Cycle.ino

@ -16,7 +16,7 @@ the box, without any further configuration:
KALEIDOSCOPE_INIT_PLUGINS(CycleTimeReport); KALEIDOSCOPE_INIT_PLUGINS(CycleTimeReport);
void setup () { void setup (void) {
Kaleidoscope.serialPort().begin(9600); Kaleidoscope.serialPort().begin(9600);
Kaleidoscope.setup (); Kaleidoscope.setup ();
} }
@ -25,27 +25,29 @@ void setup () {
## Plugin methods ## Plugin methods
The plugin provides a single object, `CycleTimeReport`, with the following The plugin provides a single object, `CycleTimeReport`, with the following
methods: property. All times are in milliseconds.
### `.setReportInterval(interval)` ### `.average_loop_time`
> Sets the length of time between reports to `interval` milliseconds. The > A read-only by contract value, the average time of main loop lengths between
> default is `1000`, so it will report once per second. > two reports.
### `.report(mean_cycle_time)` ## Overrideable methods
> Reports the average (mean) cycle time since the previous report. This method ### `cycleTimeReport()`
> is called automatically, once per report interval (see above). By default, it
> does so over `Serial`. > Reports the average loop time. By default, it does so over `Serial`, every
> time when the report period is up.
> >
> It can be overridden, to change how the report looks, or to make the report > It can be overridden, to change how the report looks, or to make the report
> toggleable, among other things. > toggleable, among other things.
> >
> It takes no arguments, and returns nothing. > It takes no arguments, and returns nothing, but has access to
> `CycleTimeReport.average_loop_time` above.
## Further reading ## Further reading
Starting from the [example][plugin:example] is the recommended way of getting Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin. started with the plugin.
[plugin:example]: /examples/Features/CycleTimeReport/CycleTimeReport.ino [plugin:example]: ../../examples/Features/CycleTimeReport/CycleTimeReport.ino

@ -5,8 +5,8 @@ be re-defined without compiling and flashing new firmware: one can change
dynamic macros via [Focus][plugin:focus], using a tool like dynamic macros via [Focus][plugin:focus], using a tool like
[Chrysalis][chrysalis]. [Chrysalis][chrysalis].
[plugin:macros]: Kaleidoscope-Macros.md [plugin:macros]: Macros.md
[plugin:focus]: Kaleidoscope-FocusSerial.md [plugin:focus]: FocusSerial.md
[chrysalis]: https://github.com/keyboardio/Chrysalis [chrysalis]: https://github.com/keyboardio/Chrysalis
Dynamic macros come with certain limitations, however: unlike the built-in Dynamic macros come with certain limitations, however: unlike the built-in
@ -19,9 +19,9 @@ except the amount of storage available on the keyboard.
## Using the plugin ## Using the plugin
To use the plugin, we need to include the header, initialize the plugin with To use the plugin, we need to include the header, tell the firmware to `use` the
`KALEIDOSCOPE_INIT_PLUGINS()`, and reserve storage space for the macros. This is plugin, and reserve storage space for the macros. This is best illustrated with
best illustrated with an example: an example:
```c++ ```c++
#include <Kaleidoscope.h> #include <Kaleidoscope.h>
@ -69,7 +69,7 @@ The plugin provides a `DynamicMacros` object, with the following methods and pro
The plugin supports the same [macro steps][doc:steps] as the Macros plugin, The plugin supports the same [macro steps][doc:steps] as the Macros plugin,
please refer to the documentation therein. please refer to the documentation therein.
[doc:steps]: (Kaleidoscope-Macros.md#macro-steps) [doc:steps]: Macros.md#macro-steps
## Focus commands ## Focus commands
@ -96,6 +96,5 @@ The plugin provides two Focus commands: `macros.map` and `macros.trigger`.
## Dependencies ## Dependencies
* [Kaleidoscope-MacroSupport](Kaleidoscope-MacroSupport.md) * [Kaleidoscope-EEPROM-Settings](EEPROM-Settings.md)
* [Kaleidoscope-EEPROM-Settings](Kaleidoscope-EEPROM-Settings.md) * [Kaleidoscope-FocusSerial](FocusSerial.md)
* [Kaleidoscope-FocusSerial](Kaleidoscope-FocusSerial.md)

@ -4,8 +4,8 @@ The `DynamicTapDance` plugin allows one to set up [TapDance][plugin:tapdance] ke
without the need to compile and flash new firmware: one can change dynamic without the need to compile and flash new firmware: one can change dynamic
dances via [Focus][plugin:focus], using a tool like [Chrysalis][chrysalis]. dances via [Focus][plugin:focus], using a tool like [Chrysalis][chrysalis].
[plugin:tapdance]: Kaleidoscope-TapDance.md [plugin:tapdance]: TapDance.md
[plugin:focus]: Kaleidoscope-FocusSerial.md [plugin:focus]: FocusSerial.md
[chrysalis]: https://github.com/keyboardio/Chrysalis [chrysalis]: https://github.com/keyboardio/Chrysalis
Dynamic dances come with certain limitations, however: unlike the built-in ones, Dynamic dances come with certain limitations, however: unlike the built-in ones,
@ -77,7 +77,7 @@ The plugin provides a `DynamicTapDance` object, with the following methods and p
## Focus commands ## Focus commands
The plugin provides one Focus command: `tapdance.map`. The plugin provides one Focus command: `tapdance.ap`.
### `tapdance.map [dances...]` ### `tapdance.map [dances...]`
@ -87,7 +87,7 @@ The plugin provides one Focus command: `tapdance.map`.
> storage space, even data after the final marker. > storage space, even data after the final marker.
> >
> With arguments, it replaces the current set of dynamic dances with the newly > With arguments, it replaces the current set of dynamic dances with the newly
> given ones. Dances are terminated by an end marker, and the last dance must be > given ones. Dances are terminated by an end marker, and the last macro must be
> terminated by an additional one. It is up to the caller to make sure these > terminated by an additional one. It is up to the caller to make sure these
> rules are obeyed. > rules are obeyed.
> >
@ -96,6 +96,6 @@ The plugin provides one Focus command: `tapdance.map`.
## Dependencies ## Dependencies
* [Kaleidoscope-EEPROM-Settings](Kaleidoscope-EEPROM-Settings.md) * [Kaleidoscope-EEPROM-Settings](EEPROM-Settings.md)
* [Kaleidoscope-FocusSerial](Kaleidoscope-FocusSerial.md) * [Kaleidoscope-FocusSerial](FocusSerial.md)
* [Kaleidoscope-TapDance](Kaleidoscope-TapDance.md) * [Kaleidoscope-TapDance](TapDance.md)

@ -114,11 +114,11 @@ in turn provides the following command:
* [Kaleidoscope-EEPROM-Keymap][plugin:eeprom-keymap] * [Kaleidoscope-EEPROM-Keymap][plugin:eeprom-keymap]
[plugin:eeprom-keymap]: Kaleidoscope-EEPROM-Keymap.md [plugin:eeprom-keymap]: EEPROM-Keymap.md
## Further reading ## Further reading
Starting from the [example][plugin:example] is the recommended way of getting Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin. started with the plugin.
[plugin:example]: /examples/Features/EEPROM/EEPROM-Keymap-Programmer/EEPROM-Keymap-Programmer.ino [plugin:example]: ../../examples/Features/EEPROM/EEPROM-Keymap-Programmer/EEPROM-Keymap-Programmer.ino

@ -4,9 +4,9 @@ While keyboards usually ship with a keymap programmed in, to be able to change t
In short, this plugin allows us to change our keymaps, without having to compile and flash new firmware. It does so through the use of the [FocusSerial][plugin:focusSerial] plugin. In short, this plugin allows us to change our keymaps, without having to compile and flash new firmware. It does so through the use of the [FocusSerial][plugin:focusSerial] plugin.
[plugin:focusSerial]: Kaleidoscope-FocusSerial.md [plugin:focusSerial]: FocusSerial.md
By default, the plugin extends the keymap in PROGMEM: it will only look for keys in EEPROM if looking up from a layer that's higher than the last one in PROGMEM. This behaviour can be changed either via `Focus` (see below), or by calling `EEPROMSettings.use_eeprom_layers_only` (see the [EEPROMSettings](Kaleidoscope-EEPROM-Settings.md) documentation for more information). By default, the plugin extends the keymap in PROGMEM: it will only look for keys in EEPROM if looking up from a layer that's higher than the last one in PROGMEM. This behaviour can be changed either via `Focus` (see below), or by calling `EEPROMSettings.use_eeprom_layers_only` (see the [EEPROMSettings](EEPROM-Settings.md) documentation for more information).
## Using the plugin ## Using the plugin
@ -61,11 +61,11 @@ The plugin provides three Focus commands: `keymap.default`, `keymap.custom`, and
## Dependencies ## Dependencies
* [Kaleidoscope-EEPROM-Settings](Kaleidoscope-EEPROM-Settings.md) * [Kaleidoscope-EEPROM-Settings](EEPROM-Settings.md)
* [Kaleidoscope-FocusSerial](Kaleidoscope-FocusSerial.md) * [Kaleidoscope-FocusSerial](FocusSerial.md)
## Further reading ## Further reading
Starting from the [example][plugin:example] is the recommended way of getting started with the plugin. Starting from the [example][plugin:example] is the recommended way of getting started with the plugin.
[plugin:example]: /examples/Features/EEPROM/EEPROM-Keymap/EEPROM-Keymap.ino [plugin:example]: ../../examples/Features/EEPROM/EEPROM-Keymap/EEPROM-Keymap.ino

@ -136,7 +136,7 @@ The plugin provides two - optional - [Focus][FocusSerial] command plugins:
to `KALEIDOSCOPE_INIT_PLUGINS` if one wishes to use them. They provide the to `KALEIDOSCOPE_INIT_PLUGINS` if one wishes to use them. They provide the
following commands: following commands:
[FocusSerial]: Kaleidoscope-FocusSerial.md [FocusSerial]: FocusSerial.md
### `settings.defaultLayer` ### `settings.defaultLayer`
@ -172,18 +172,13 @@ following commands:
> Returns the amount of free bytes in `EEPROM`. > Returns the amount of free bytes in `EEPROM`.
### `eeprom.erase`
> Erases the entire `EEPROM`, and reboots the keyboard to make sure the erase is
> picked up by every single plugin.
## Dependencies ## Dependencies
* (Kaleidoscope-FocusSerial)[Kaleidoscope-FocusSerial.md] * [Kaleidoscope-FocusSerial][FocusSerial]
## Further reading ## Further reading
Starting from the [example][plugin:example] is the recommended way of getting Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin. started with the plugin.
[plugin:example]: /examples/Features/EEPROM/EEPROM-Settings/EEPROM-Settings.ino [plugin:example]: ../../examples/Features/EEPROM/EEPROM-Settings/EEPROM-Settings.ino

@ -0,0 +1,41 @@
# Escape-OneShot
Turn the `Esc` key into a special key, that can cancel any active `OneShot`
effect - or act as the normal `Esc` key if none are active, or if any of them
are still held. For those times when one accidentally presses a one-shot key, or
change their minds.
## Using the plugin
To use the plugin, one needs to include the header, and activate it. No further
configuration is necessary.
```c++
#include <Kaleidoscope.h>
#include <Kaleidoscope-OneShot.h>
#include <Kaleidoscope-Escape-OneShot.h>
KALEIDOSCOPE_INIT_PLUGINS(OneShot,
EscapeOneShot);
void setup () {
Kaleidoscope.setup ();
}
```
The plugin only makes sense when using one-shot keys.
## Plugin methods
The plugin provides the `EscapeOneShot` object, which has no public methods.
## Dependencies
* [Kaleidoscope-OneShot](OneShot.md)
## Further reading
Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin.
[plugin:example]: ../../examples/Keystrokes/Escape-OneShot/Escape-OneShot.ino

@ -47,16 +47,16 @@ The plugin provides the `FingerPainter` object, which provides no public methods
## Dependencies ## Dependencies
* [Kaleidoscope-EEPROM-Settings](Kaleidoscope-EEPROM-Settings.md) * [Kaleidoscope-EEPROM-Settings](EEPROM-Settings.md)
* [Kaleidoscope-FocusSerial](Kaleidoscope-FocusSerial.md) * [Kaleidoscope-FocusSerial](FocusSerial.md)
* [Kaleidoscope-LED-Palette-Theme][plugin:l-p-t] * [Kaleidoscope-LED-Palette-Theme][plugin:l-p-t]
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)
[plugin:l-p-t]: Kaleidoscope-LED-Palette-Theme.md [plugin:l-p-t]: LED-Palette-Theme.md
## Further reading ## Further reading
Starting from the [example][plugin:example] is the recommended way of getting Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin. started with the plugin.
[plugin:example]: /examples/LEDs/FingerPainter/FingerPainter.ino [plugin:example]: ../../examples/LEDs/FingerPainter/FingerPainter.ino

@ -32,7 +32,7 @@ void setup () {
The plugin provides a single [Focus][FocusSerial] command: The plugin provides a single [Focus][FocusSerial] command:
[FocusSerial]: Kaleidoscope-FocusSerial.md [FocusSerial]: FocusSerial.md
### `firmware.dump` ### `firmware.dump`
@ -40,4 +40,4 @@ The plugin provides a single [Focus][FocusSerial] command:
## Dependencies ## Dependencies
* [Kaleidoscope-FocusSerial][Kaleidoscope-FocusSerial.md] * [Kaleidoscope-FocusSerial][FocusSerial]

@ -4,7 +4,7 @@ Bidirectional communication for Kaleidoscope. With this plugin enabled, plugins
This plugin is an upgrade of the former [Kaleidoscope-Focus][kaleidoscope:focus] plugin. See the [UPGRADING.md][upgrading] document for information about how to transition to the new system. This plugin is an upgrade of the former [Kaleidoscope-Focus][kaleidoscope:focus] plugin. See the [UPGRADING.md][upgrading] document for information about how to transition to the new system.
[kaleidoscope:focus]: (Kaleidoscope-FocusSerial.md) [kaleidoscope:focus]: https://github.com/keyboardio/Kaleidoscope-Focus
[upgrading]: ../../UPGRADING.md#bidirectional-communication-for-plugins [upgrading]: ../../UPGRADING.md#bidirectional-communication-for-plugins
## Using the plugin ## Using the plugin
@ -20,17 +20,10 @@ Nevertheless, a very simple example is shown below:
namespace kaleidoscope { namespace kaleidoscope {
class FocusTestCommand : public Plugin { class FocusTestCommand : public Plugin {
public: public:
EventHandlerResult onNameQuery() { FocusTestCommand() {}
return ::Focus.sendName(F("FocusTestCommand"));
}
EventHandlerResult onFocusEvent(const char *input) {
const char *cmd = PSTR("test");
if (::Focus.inputMatchesHelp(input))
return ::Focus.printHelp(cmd);
if (!::Focus.inputMatchesCommand(input, cmd)) EventHandlerResult onFocusEvent(const char *command) {
if (strcmp_P(command, PSTR("test")) != 0)
return EventHandlerResult::OK; return EventHandlerResult::OK;
::Focus.send(F("Congratulations, the test command works!")); ::Focus.send(F("Congratulations, the test command works!"));
@ -52,18 +45,6 @@ void setup () {
The plugin provides the `Focus` object, with a couple of helper methods aimed at developers. Terminating the response with a dot on its own line is handled implicitly by `FocusSerial`, one does not need to do that explicitly. The plugin provides the `Focus` object, with a couple of helper methods aimed at developers. Terminating the response with a dot on its own line is handled implicitly by `FocusSerial`, one does not need to do that explicitly.
### `.inputMatchesHelp(input)`
Returns `true` if the given `input` matches the `help` command. To be used at the top of `onFocusEvent()`, followed by `.printHelp(...)`.
### `.printHelp(...)`
Given a series of strings (stored in `PROGMEM`, via `PSTR()`), prints them one per line. Assumes it is run as part of handling the `help` command. Returns `EventHandlerResult::OK`.
### `.inputMatchesCommand(input, command)`
Returns `true` if the `input` matches the expected `command`, false otherwise. A convenience function over `strcmp_P()`.
### `.send(...)` ### `.send(...)`
### `.sendRaw(...)` ### `.sendRaw(...)`
@ -71,13 +52,6 @@ Sends a list of variables over the wire. The difference between `.send()` and `.
Both of them take a variable number of arguments, of almost any type: all built-in types can be sent, `cRGB`, `Key` and `bool` too in addition. For colors, `.send()` will write them as an `R G B` sequence; `Key` objects will be sent as the raw 16-bit keycode; and `bool` will be sent as either the string `true`, or `false`. Both of them take a variable number of arguments, of almost any type: all built-in types can be sent, `cRGB`, `Key` and `bool` too in addition. For colors, `.send()` will write them as an `R G B` sequence; `Key` objects will be sent as the raw 16-bit keycode; and `bool` will be sent as either the string `true`, or `false`.
### `.sendName(F("..."))`
To be used with the `onNameQuery()` hook, this sends the plugin name given,
followed by a newline, and returns `EventHandlerResult::OK`, so that
`onNameQuery()` hooks can be implemented in a single line with the help of this
function.
### `.read(variable)` ### `.read(variable)`
Depending on the type of the variable passed by reference, reads a 8 or 16-bit unsigned integer, a `Key`, or a `cRGB` color from the wire, into the variable passed as the argument. Depending on the type of the variable passed by reference, reads a 8 or 16-bit unsigned integer, a `Key`, or a `cRGB` color from the wire, into the variable passed as the argument.
@ -149,4 +123,4 @@ the keyboard responds.
Starting from the [example][plugin:example] is the recommended way of getting started with the plugin. Starting from the [example][plugin:example] is the recommended way of getting started with the plugin.
[plugin:example]: /examples/Features/FocusSerial/FocusSerial.ino [plugin:example]: ../../examples/Features/FocusSerial/FocusSerial.ino

@ -22,16 +22,16 @@ that.
#include <Kaleidoscope-GhostInTheFirmware.h> #include <Kaleidoscope-GhostInTheFirmware.h>
#include <Kaleidoscope-Macros.h> #include <Kaleidoscope-Macros.h>
const macro_t *macroAction(uint8_t macro_id, KeyEvent& event) { const macro_t *macroAction(uint8_t macro_index, uint8_t key_state) {
if (macro_id == 0 && keyToggledOn(event.state)) if (macro_index == 0 && keyToggledOn(key_state))
GhostInTheFirmware.activate(); GhostInTheFirmware.activate();
return MACRO_NONE; return MACRO_NONE;
} }
static const kaleidoscope::plugin::GhostInTheFirmware::GhostKey ghost_keys[] PROGMEM = { static const kaleidoscope::plugin::GhostInTheFirmware::GhostKey ghost_keys[] PROGMEM = {
{KeyAddr(0, 0), 200, 50}, {0, 0, 200, 50},
{KeyAddr::none(), 0, 0} {0, 0, 0}
}; };
KALEIDOSCOPE_INIT_PLUGINS(GhostInTheFirmware, KALEIDOSCOPE_INIT_PLUGINS(GhostInTheFirmware,
@ -59,14 +59,13 @@ methods and properties:
### `.ghost_keys` ### `.ghost_keys`
> Set this property to the sequence of keys to press, by assigning a sequence to > Set this property to the sequence of keys to press, by assigning a sequence to
> this variable. Each element is a `GhostKey` object, comprised of a `KeyAddr` > this variable. Each element is a quartett of `row`, `column`, a `pressTime`,
> (the location of a key on the keyboard), a duration of the key press (in > and a `delay`. Each of these will be pressed in different cycles, unlike
> milliseconds), and a delay after the key release until the next one is pressed > macros which play back within a single cycle.
> (also in milliseconds). >
> The key at `row`, `column` will be held for `pressTime` milliseconds, and
> This `ghost_keys` array *MUST* end with the sentinal value of > after an additional `delay` milliseconds, the plugin will move on to the next
> `{KeyAddr::none(), 0, 0}` to ensure that GhostInTheFirmware doesn't read past > entry in the sequence.
> the end of the array.
> >
> The sequence *MUST* reside in `PROGMEM`. > The sequence *MUST* reside in `PROGMEM`.
@ -75,4 +74,4 @@ methods and properties:
Starting from the [example][plugin:example] is the recommended way of getting Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin. started with the plugin.
[plugin:example]: /examples/Features/GhostInTheFirmware/GhostInTheFirmware.ino [plugin:example]: ../../examples/Features/GhostInTheFirmware/GhostInTheFirmware.ino

@ -79,11 +79,11 @@ and properties:
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)
## Further reading ## Further reading
Starting from the [example][plugin:example] is the recommended way of getting Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin. started with the plugin.
[plugin:example]: /examples/LEDs/Heatmap/Heatmap.ino [plugin:example]: ../../examples/LEDs/Heatmap/Heatmap.ino

@ -11,7 +11,7 @@ can then be reused by other plugins.
See the [Unicode][plugin:unicode] extension for an example about how to use See the [Unicode][plugin:unicode] extension for an example about how to use
`HostOS` in practice. `HostOS` in practice.
[plugin:unicode]: Kaleidoscope-Unicode.md [plugin:unicode]: Unicode.md
## Using the extension ## Using the extension
@ -21,18 +21,18 @@ The extension provides a `HostOS` singleton object.
#include <Kaleidoscope.h> #include <Kaleidoscope.h>
#include <Kaleidoscope-HostOS.h> #include <Kaleidoscope-HostOS.h>
void someFunction() { void someFunction(void) {
if (HostOS.os() == kaleidoscope::hostos::LINUX) { if (HostOS.os() == kaleidoscope::hostos::LINUX) {
// do something linux-y // do something linux-y
} }
if (HostOS.os() == kaleidoscope::hostos::MACOS) { if (HostOS.os() == kaleidoscope::hostos::OSX) {
// do something macOS-y // do something OSX-y
} }
} }
KALEIDOSCOPE_INIT_PLUGINS(HostOS) KALEIDOSCOPE_INIT_PLUGINS(HostOS)
void setup() { void setup(void) {
Kaleidoscope.setup (); Kaleidoscope.setup ();
} }
``` ```
@ -55,13 +55,10 @@ The extension provides the following methods on the `HostOS` singleton:
The OS type (i.e. the return type of `.os()` and the arguments to `.os(type)`) will be one of the following: The OS type (i.e. the return type of `.os()` and the arguments to `.os(type)`) will be one of the following:
- `kaleidoscope::hostos::LINUX` - `kaleidoscope::hostos::LINUX`
- `kaleidoscope::hostos::MACOS` - `kaleidoscope::hostos::OSX`
- `kaleidoscope::hostos::WINDOWS` - `kaleidoscope::hostos::WINDOWS`
- `kaleidoscope::hostos::OTHER` - `kaleidoscope::hostos::OTHER`
For compability reasons, `kaleidoscope::hostos::OSX` is an alias to
`kaleidoscope::hostos::MACOS`.
## Focus commands ## Focus commands
The plugin provides the `FocusHostOSCommand` object, which, when enabled, The plugin provides the `FocusHostOSCommand` object, which, when enabled,
@ -77,11 +74,11 @@ provides the `hostos.type` Focus command.
## Dependencies ## Dependencies
* [Kaleidoscope-EEPROM-Settings](Kaleidoscope-EEPROM-Settings.md) * [Kaleidoscope-EEPROM-Settings](EEPROM-Settings.md)
## Further reading ## Further reading
Starting from the [example][plugin:example] is the recommended way of getting Starting from the [example][plugin:example] is the recommended way of getting
started with the extension. started with the extension.
[plugin:example]: /examples/Features/HostOS/HostOS.ino [plugin:example]: ../../examples/Features/HostOS/HostOS.ino

@ -23,7 +23,7 @@ void setup () {
The plugin provides the `HostPowerManagement` object, with no public methods. The plugin provides the `HostPowerManagement` object, with no public methods.
## Overridable methods ## Overrideable methods
### `hostPowerManagementEventHandler(event)` ### `hostPowerManagementEventHandler(event)`
@ -44,7 +44,7 @@ The plugin provides the `HostPowerManagement` object, with no public methods.
Starting from the [example][plugin:example] is the recommended way of getting Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin. started with the plugin.
[plugin:example]: /examples/Features/HostPowerManagement/HostPowerManagement.ino [plugin:example]: ../../examples/Features/HostPowerManagement/HostPowerManagement.ino
## Caveats ## Caveats

@ -25,7 +25,7 @@ the box, without any further configuration:
KALEIDOSCOPE_INIT_PLUGINS(LEDControl, IdleLEDs, LEDEffectRainbowWave); KALEIDOSCOPE_INIT_PLUGINS(LEDControl, IdleLEDs, LEDEffectRainbowWave);
void setup () { void setup (void) {
Kaleidoscope.setup (); Kaleidoscope.setup ();
} }
``` ```
@ -54,7 +54,7 @@ KALEIDOSCOPE_INIT_PLUGINS(
LEDEffectRainbowWave LEDEffectRainbowWave
); );
void setup () { void setup (void) {
Kaleidoscope.setup (); Kaleidoscope.setup ();
} }
``` ```
@ -96,7 +96,7 @@ the following properties and methods.
The plugin provides a single [Focus][FocusSerial] command, but only when using The plugin provides a single [Focus][FocusSerial] command, but only when using
the `PersistentIdleLEDs` variant: the `PersistentIdleLEDs` variant:
[FocusSerial]: Kaleidoscope-FocusSerial.md [FocusSerial]: FocusSerial.md
### `idleleds.time_limit [seconds]` ### `idleleds.time_limit [seconds]`
@ -108,16 +108,16 @@ the `PersistentIdleLEDs` variant:
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)
### Optional dependencies ### Optional dependencies
* [Kaleidoscope-EEPROM-Settings](Kaleidoscope-EEPROM-Settings.md) * [Kaleidoscope-EEPROM-Settings](EEPROM-Settings.md)
* [FocusSerial](Kaleidoscope-FocusSerial.md) * [FocusSerial](FocusSerial.md)
## Further reading ## Further reading
Starting from the [example][plugin:example] is the recommended way of getting Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin. started with the plugin.
[plugin:example]: /examples/LEDs/IdleLEDs/IdleLEDs.ino [plugin:example]: ../../examples/LEDs/IdleLEDs/IdleLEDs.ino

@ -1,7 +1,7 @@
# LED-ActiveLayerColor # LED-ActiveLayerColor
A simple way to light up the keyboard in uniform colors, depending on what layer A simple way to light up the keyboard in uniform colors, depending on what layer
one's on. Unlike [Colormap](Kaleidoscope-Colormap.md), all keys will be the same color. But one's on. Unlike [Colormap](Colormap.md), all keys will be the same color. But
this plugin uses considerably less resources, and is easier to set up as well. A this plugin uses considerably less resources, and is easier to set up as well. A
perfect solution when one wants to quickly see what layer they're on, with perfect solution when one wants to quickly see what layer they're on, with
minimal resources and time investment. minimal resources and time investment.
@ -43,11 +43,11 @@ method:
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)
## Further reading ## Further reading
Starting from the [example][plugin:example] is the recommended way of getting Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin. started with the plugin.
[plugin:example]: /examples/LEDs/LED-ActiveLayerColor/LED-ActiveLayerColor.ino [plugin:example]: ../../examples/LEDs/LED-ActiveLayerColor/LED-ActiveLayerColor.ino

@ -0,0 +1,64 @@
# LED-ActiveModColor
With this plugin, any active modifier on the keyboard will have the LED under it
highlighted. No matter how the modifier got activated (a key press, a macro,
anything else), the coloring will apply. Layer keys, be them layer toggles,
momentary switches, or one-shot layer keys count as modifiers as far as the
plugin is concerned.
## Using the plugin
To use the plugin, one needs to include the header, and activate the effect. It
is also possible to use a custom color instead of the white default.
```c++
#include <Kaleidoscope.h>
#include <Kaleidoscope-LEDControl.h>
#include <Kaleidoscope-LED-ActiveModColor.h>
KALEIDOSCOPE_INIT_PLUGINS(LEDControl,
ActiveModColorEffect);
void setup () {
Kaleidoscope.setup ();
ActiveModColorEffect.highlight_color = CRGB(0x00, 0xff, 0xff);
}
```
It is recommended to place the activation (the `KALEIDOSCOPE_INIT_PLUGINS` parameter) of the
plugin last, so that it can reliably override any other plugins that may work
with the LEDs, and apply the highlight over those.
## Plugin properties
The plugin provides the `ActiveModColorEffect` object, which has the following
properties:
### `.highlight_color`
> The color to use for highlighting the modifiers. Defaults to a white color.
### `.sticky_color`
> The color to use for highlighting one-shot modifiers when they are sticky. Defaults to a red color.
## Plugin methods
The `ActiveModColorEffect` object provides the following methods:
### `.highlightNormalModifiers(bool)`
> Can be used to enable or disable the highlighting of normal modifiers. Defaults to true.
## Dependencies
* [Kaleidoscope-LEDControl](LEDControl.md)
* [Kaleidoscope-OneShot](OneShot.md)
## Further reading
Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin.
[plugin:example]: ../../examples/LEDs/LED-ActiveModColor/LED-ActiveModColor.ino

@ -92,11 +92,11 @@ been an exaggeration, there is only one as of this writing:
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)
## Further reading ## Further reading
Starting from the [example][plugin:example] is the recommended way of getting Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin. started with the plugin.
[plugin:example]: /examples/LEDs/LED-AlphaSquare/LED-AlphaSquare.ino [plugin:example]: ../../examples/LEDs/LED-AlphaSquare/LED-AlphaSquare.ino

@ -20,11 +20,14 @@ full extent, we need to create our own plugin on top of it.
namespace example { namespace example {
class TestLEDMode : public LEDMode { class TestLEDMode : public LEDMode {
public:
TestLEDMode() {}
protected: protected:
void setup() final; void setup(void) final;
void update() final; void update(void) final;
kaleidoscope::EventHandlerResult onFocusEvent(const char *input); kaleidoscope::EventHandlerResult onFocusEvent(const char *command);
private: private:
static uint16_t map_base_; static uint16_t map_base_;
@ -32,17 +35,17 @@ class TestLEDMode : public LEDMode {
uint16_t TestLEDMode::map_base_; uint16_t TestLEDMode::map_base_;
void TestLEDMode::setup() { void TestLEDMode::setup(void) {
map_base_ = LEDPaletteTheme.reserveThemes(1); map_base_ = LEDPaletteTheme.reserveThemes(1);
} }
void TestLEDMode::update() { void TestLEDMode::update(void) {
LEDPaletteTheme.updateHandler(map_base_, 0); LEDPaletteTheme.updateHandler(map_base_, 0);
} }
kaleidoscope::EventHandlerResult kaleidoscope::EventHandlerResult
TestLEDMode::onFocusEvent(const char *input) { TestLEDMode::onFocusEvent(const char *command) {
return LEDPaletteTheme.themeFocusEvent(input, PSTR("testLedMode.map"), map_base_, 1); return LEDPaletteTheme.themeFocusEvent(command, PSTR("testLedMode.map"), map_base_, 1);
} }
} }
@ -113,13 +116,13 @@ The plugin provides the `LEDPaletteTheme` object, which has the following method
## Dependencies ## Dependencies
* [Kaleidoscope-EEPROM-Settings](Kaleidoscope-EEPROM-Settings.md) * [Kaleidoscope-EEPROM-Settings](EEPROM-Settings.md)
* [Kaleidoscope-FocusSerial](Kaleidoscope-FocusSerial.md) * [Kaleidoscope-FocusSerial](FocusSerial.md)
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)
## Further reading ## Further reading
Starting from the [example][plugin:example] is the recommended way of getting Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin. started with the plugin.
[plugin:example]: /examples/LEDs/LED-Palette-Theme/LED-Palette-Theme.ino [plugin:example]: ../../examples/LEDs/LED-Palette-Theme/LED-Palette-Theme.ino

@ -80,11 +80,11 @@ The plugin provides the following effects:
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)
## Further reading ## Further reading
Starting from the [example][plugin:example] is the recommended way of getting Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin. started with the plugin.
[plugin:example]: /examples/LEDs/LED-Stalker/LED-Stalker.ino [plugin:example]: ../../examples/LEDs/LED-Stalker/LED-Stalker.ino

@ -51,11 +51,11 @@ properties:
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)
## Further reading ## Further reading
Starting from the [example][plugin:example] is the recommended way of getting Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin. started with the plugin.
[plugin:example]: /examples/LEDs/LED-Wavepool/LED-Wavepool.ino [plugin:example]: ../../examples/LEDS/LED-Wavepool/LED-Wavepool.ino

@ -1,4 +1,4 @@
# Kaleidoscope-LEDControl # LEDControl
This is a plugin for [Kaleidoscope][fw], for controlling the LEDs, and LED This is a plugin for [Kaleidoscope][fw], for controlling the LEDs, and LED
effects. effects.

@ -42,4 +42,4 @@ properties:
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)

@ -143,4 +143,4 @@ properties:
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)

@ -36,4 +36,4 @@ The plugin provides the `LEDBreatheEffect` object, which has a single property:
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)

@ -41,4 +41,4 @@ outside of those provided by all LED modes:
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)

@ -48,4 +48,4 @@ both of which provide the following methods:
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)

@ -23,4 +23,4 @@ void setup() {
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save