Compare commits

..

7 Commits

Author SHA1 Message Date
Jesse Vincent 290a4db2d4
The simulator hadn't been updated for these changes
4 years ago
Gergely Nagy cacca53496
Update the virtual device & tests for the merged KeyboardioHID
4 years ago
Gergely Nagy 35a8170cec
driver/hid/keyboardio: Move the actual implementation to hid/keyboardio/usb
4 years ago
Gergely Nagy b1e2a889bb
dygma/Raise: Drop the KeyboardioHID.h include
4 years ago
Gergely Nagy 35b1fb239c
driver/keyboardioHID: astyle
4 years ago
Gergely Nagy 2c4a274cb3
driver/KeyboardioHID: make cpplint happy
4 years ago
Gergely Nagy ada56fe6b9
Merge KeyboardioHID as (part of) a driver
4 years ago

@ -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

@ -4,63 +4,49 @@ on: [push, pull_request]
env: env:
LC_ALL: C LC_ALL: C
KALEIDOSCOPE_CCACHE: 1
ARDUINO_DIRECTORIES_USER: ${{ github.workspace }}/.arduino/user ARDUINO_DIRECTORIES_USER: ${{ github.workspace }}/.arduino/user
CLANG_FORMAT_CMD: clang-format-12
jobs: jobs:
smoke-sketches: smoke-sketches:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Cache arduino dep downloads
uses: actions/cache@v2 ## We delete the Bundle's version of Kaleidoscope, and symlink ourselves in.
with: ## This makes sure we're using the current version of the library.
path: ${{ github.workspace}}/.arduino/downloads - run: sudo apt-get install ccache
key: ${{ runner.os }}-arduino-downloads
- run: make setup - run: make setup
- run: KALEIDOSCOPE_TEMP_PATH=${{ github.workspace}}/.kaleidoscope-temp make -j $(nproc) smoke-sketches - run: make -j $(nproc) smoke-sketches
run-google-tests: run-google-tests:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Cache arduino dep downloads # - uses: mxschmitt/action-tmate@v3
uses: actions/cache@v2 - run: sudo apt-get install ccache
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: make setup
- run: KALEIDOSCOPE_CCACHE=1 make -j $(nproc) --output-sync=recurse simulator-tests - run: make -j $(nproc) simulator-tests
check-code-style: check-astyle:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- run: sudo apt-get install astyle
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: KALEIDOSCOPE_CODE_FORMATTER=clang-format-12 make check-code-style - run: make setup
- run: make check-astyle
check-shellcheck: check-shellcheck:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: make setup
- run: make shellcheck - run: make shellcheck
check-cpplint: check-cpplint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: make setup
- run: make cpplint - run: make cpplint
find-filename-conflicts: find-filename-conflicts:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - 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 setup
- run: make build-arduino-nightly-package - run: make find-filename-conflicts

2
.gitignore vendored

@ -5,7 +5,6 @@
/output/ /output/
/examples/*/output/ /examples/*/output/
/out/ /out/
/docs/plugins
/docs/generated /docs/generated
/docs/doxyoutput /docs/doxyoutput
/docs/api /docs/api
@ -13,4 +12,3 @@
/results/ /results/
generated-testcase.cpp generated-testcase.cpp
.arduino .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

@ -1,31 +1,3 @@
# Reset a bunch of historical GNU make implicit rules that we never
# use, but which have a disastrous impact on performance
#
# --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
%:: %,v
%:: RCS/%,v
%:: RCS/%
%:: s.%
%:: SCCS/s.%
.SUFFIXES:
# GNU Make earlier than 4.0 don't have the output-sync option, but we need it
# to make parallel simulator test output readable. (otherwise it gets interleaved)
ifeq ($(shell test $(firstword $(subst ., ,$(MAKE_VERSION))) -ge 4; echo $$?),0)
MAKEFLAGS+=--output-sync=target
else
_using_old_make=1
endif
include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/etc/makefiles/arduino-cli.mk include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/etc/makefiles/arduino-cli.mk
# Set up an argument for passing to the simulator tests in docker # Set up an argument for passing to the simulator tests in docker
@ -36,135 +8,104 @@ ifneq ($(TEST_PATH),)
TEST_PATH_ARG="TEST_PATH='$(TEST_PATH)'" TEST_PATH_ARG="TEST_PATH='$(TEST_PATH)'"
endif endif
.DEFAULT_GOAL := smoke-sketches
.PHONY: setup DEFAULT_GOAL: smoke-sketches
setup: $(ARDUINO_CLI_PATH) $(ARDUINO_DIRECTORIES_DATA)/arduino-cli.yaml install-arduino-core-avr install-arduino-core-kaleidoscope checkout-platform prepare-virtual
PLUGIN_TEST_SUPPORT_DIR ?= $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/build-tools/
PLUGIN_TEST_BIN_DIR ?= $(PLUGIN_TEST_SUPPORT_DIR)/../toolchain/$(shell gcc --print-multiarch)/bin
setup: $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/avr/boards.txt $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/virtual/boards.txt $(ARDUINO_CLI_PATH) configure-arduino-cli install-arduino-core-avr
@: @:
.PHONY: checkout-platform
checkout-platform: $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/avr/boards.txt checkout-platform: $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/avr/boards.txt
@: @:
$(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/avr/boards.txt:
git clone -c core.symlinks=true \
--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 prepare-virtual: $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/virtual/boards.txt
@: @:
$(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 $(MAKE) -C $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio prepare-virtual
.PHONY: update
update: $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/avr/boards.txt:
cd $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio; git pull; \ git clone -c core.symlinks=true --recurse-submodules=":(exclude)avr/libraries/Kaleidoscope" --recurse-submodules=build-tools --recurse-submodules=toolchain --recurse-submodules=avr/libraries/ git://github.com/keyboardio/Kaleidoscope-Bundle-Keyboardio $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio
git submodule update --init --recursive rm -d $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/avr/libraries/Kaleidoscope
cd $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/gd32; git pull; \ ln -s $(KALEIDOSCOPE_DIR) $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/avr/libraries/Kaleidoscope
git submodule update --init --recursive
simulator-tests: configure-arduino-cli $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/virtual/boards.txt
.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
$(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)" ARDUINO_DIRECTORIES_USER="$(ARDUINO_DIRECTORIES_USER)" ./bin/run-docker "make simulator-tests $(TEST_PATH_ARG)"
.PHONY: docker-clean
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/*" ARDUINO_DIRECTORIES_USER="$(ARDUINO_DIRECTORIES_USER)" ./bin/run-docker "make clean"
.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" ARDUINO_DIRECTORIES_USER="$(ARDUINO_DIRECTORIES_USER)" ./bin/run-docker "bash"
run-tests: $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/virtual/boards.txt 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 prepare-virtual checkout-platform adjust-git-timestamps docker-bash docker-simulator-tests run-tests simulator-tests setup
--exclude-dir 'testing/googletest' \
--exclude-file 'generated-testcase.cpp' \ astyle:
src plugins examples testing find ./* -type f \( -name '*.h' -o -name '*.cpp' -o -name '*.ino' \) | grep -v "testing/googletest" | xargs -n 1 astyle --project
.PHONY: check-code-style check-astyle: astyle
check-code-style: if ! git diff --exit-code; then \
bin/format-code.py \ >&2 echo "'astyle' found code style differences. Please make astyle and commit your changes"; \
--exclude-dir 'testing/googletest' \ exit 1; \
--exclude-file 'generated-testcase.cpp' \ fi; \
--check \ exit 0;
--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 -bin/cpplint.py --filter=-legal/copyright,-build/include,-readability/namespace,-whitespace/line_length,-runtime/references --recursive --extensions=cpp,h,ino src examples
.PHONY: cpplint
cpplint: cpplint:
bin/cpplint.py --config=.cpplint --quiet --recursive src plugins examples bin/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
SMOKE_SKETCHES := $(sort $(shell if [ -d ./examples ]; then find ./examples -type f -name \*ino | xargs -n 1 dirname; 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: $(SMOKE_SKETCHES) smoke-sketches: $(SMOKE_SKETCHES)
@echo "Smoke-tested all the sketches" @echo "Smoke-tested all the sketches"
.PHONY: force .PHONY: force all clean test
$(SMOKE_SKETCHES): force
$(MAKE) -C $@ -f $(KALEIDOSCOPE_ETC_DIR)/makefiles/sketch.mk compile
.PHONY: clean
clean: clean:
$(MAKE) -C tests clean $(MAKE) -C tests clean
rm -rf -- "testing/googletest/build/*" rm -rf -- "testing/googletest/build/*"
rm -rf -- "_build/*" rm -rf -- "_build/*"
build-arduino-nightly-package:
perl bin/build-arduino-package \ $(SMOKE_SKETCHES): force
--kaleidoscope-tag=master \ $(MAKE) -C $@ -f $(KALEIDOSCOPE_ETC_DIR)/makefiles/sketch.mk compile
--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

@ -5,14 +5,14 @@ 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
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) 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)
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. If you prefer to work from the command line or intend to work on Kaleidscope itself, please follow the instructions below
# Use git to check out a copy of Kaleidoscope # Use git to check out a copy of Kaleidoscope
@ -25,7 +25,7 @@ $ mkdir ${HOME}/git
$ cd ${HOME}/git $ cd ${HOME}/git
``` ```
2. Download the latest version of Kaleidoscope 2. Use git to download the latest version of Kaleidoscope
```sh ```sh
$ git clone https://github.com/keyboardio/Kaleidoscope $ git clone https://github.com/keyboardio/Kaleidoscope

@ -7,24 +7,52 @@
################### ###################
################### ###################
my $executed_as = join( ' ', @ARGV );
use warnings; use warnings;
use strict; use strict;
use File::Temp qw/tempdir/; use File::Temp qw/tempdir/;
use Cwd qw/abs_path cwd/; use Cwd qw/abs_path cwd/;
use JSON::PP; use JSON;
use Getopt::Long;
my $sha256 = 0;
my $size = 0;
my $tag = shift || 'master';
my $dir = tempdir( CLEANUP => 0 );
my $checkout_dir = "Kaleidoscope-$tag";
my $filename = $checkout_dir . ".tar.bz2";
my $checkout_path = "$dir/$checkout_dir";
chdir($dir);
`git clone --depth=1 https://github.com/keyboardio/Kaleidoscope-Bundle-Keyboardio $checkout_path`;
chdir($checkout_path);
`git checkout $tag`;
`make update-submodules`;
`find $checkout_path -name .git |xargs rm -rf`;
`rm -rf $checkout_path/toolchain`;
`rm -rf $checkout_path/etc`;
`rm -rf $checkout_path/doc`;
chdir($dir);
`rm -rf $checkout_dir/avr/build-tools`;
`mv $checkout_dir/avr $checkout_dir/$checkout_dir`; # A hack to get consistent naming with the old setup
`tar cjvf $filename -C $checkout_dir/ $checkout_dir `;
$sha256 = `sha256sum $filename | cut -d' ' -f 1 `;
chomp($sha256);
die "There was a problem generating the sha256" unless ($sha256);
$size = -s $filename;
my @ARCHES = (qw(avr gd32)); `git clone https://github.com/keyboardio/boardsmanager`;
`mkdir -p boardsmanager/builds`;
`cp $filename boardsmanager/builds/`;
my $platforms_template = { my $platform_template = {
avr => { 'archiveFileName' => 'Arduino-Boards-v1.14.zip',
'toolsDependencies' => [ 'toolsDependencies' => [
{ {
"packager" => "arduino", "packager" => "arduino",
"name" => "avr-gcc", "name" => "avr-gcc",
"version" => "7.3.0-atmel3.6.1-arduino7" "version" => "7.3.0-atmel3.6.1-arduino5"
}, },
{ {
"packager" => "arduino", "packager" => "arduino",
@ -32,7 +60,12 @@ my $platforms_template = {
"version" => "6.3.0-arduino17" "version" => "6.3.0-arduino17"
} }
], ],
'name' => "Kaleidoscope keyboards", 'url' => 'https://github.com/keyboardio/Arduino-Boards/archive/v1.14.zip',
'name' => 'keyboardio',
'version' => '1.1.4',
'checksum' =>
'SHA-256:fd0017ea2950f6fb3afb46a503f0193c75111d68dfc95fa2775fbd63f0cf4528',
'size' => '1008405',
'boards' => [ 'boards' => [
{ 'name' => 'Keyboardio Model 01' }, { 'name' => 'Keyboardio Model 01' },
{ 'name' => 'Keyboardio Atreus' } { 'name' => 'Keyboardio Atreus' }
@ -42,255 +75,37 @@ my $platforms_template = {
'help' => { 'help' => {
'online' => 'https://community.keyboard.io' '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 ); my $version = $tag;
deploy_build( $builds_dir, $filename ); if ($version =~ /^v(\d.*)$/) {
$version = $1
} };
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 { $platform_template->{archiveFileName} = $filename;
if ( -d 'plugins' ) { $platform_template->{version} = $version;
chdir("plugins"); $platform_template->{url} =
'https://raw.githubusercontent.com/keyboardio/boardsmanager/master/builds/'
. $filename;
$platform_template->{checksum} = 'SHA-256:' . $sha256;
$platform_template->{size} = $size;
# move the plugins to be fullfledged arduino libraries my $json = JSON->new->allow_nonref;
`mv * ../../`;
}
}
sub update_index_file {
my $output_dir = shift;
my $index_filename = shift;
my $json = JSON::PP->new->allow_nonref;
local $/; local $/;
open( my $fh, '<', 'boardsmanager/package_keyboardio_index.json' );
my $index_path = $output_dir . '/' . $index_filename;
open( my $fh, '<', $index_path ) || die "Could not open $index_path $!";
my $json_text = <$fh>; my $json_text = <$fh>;
my $data = decode_json($json_text); my $data = from_json($json_text);
#my $this_packager = $data->{'packages'}->[0]->{'name'}; push @{ $data->{'packages'}->[0]->{'platforms'} }, $platform_template;
#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); my $json_out = $json->canonical->pretty->encode($data);
open( my $out_fh, '>', $index_path );
open( my $out_fh, '>', 'boardsmanager/package_keyboardio_index.json' );
print $out_fh $json_out; print $out_fh $json_out;
close($out_fh); close($out_fh);
}
sub commit_results { # rm -rf /tmp/boardsmanager
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 { print
my $builds_dir = shift; "Now, you need to cd to $dir/boardsmanager check the content and commit it\n";
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)

534
bin/cpplint.py vendored

@ -41,11 +41,6 @@ We do a small hack, which is to ignore //'s with "'s after them on the
same line, but it is far from perfect (in either direction). same line, but it is far from perfect (in either direction).
""" """
# cpplint predates fstrings
# pylint: disable=consider-using-f-string
# pylint: disable=invalid-name
import codecs import codecs
import copy import copy
import getopt import getopt
@ -64,7 +59,7 @@ import xml.etree.ElementTree
# if empty, use defaults # if empty, use defaults
_valid_extensions = set([]) _valid_extensions = set([])
__VERSION__ = '1.6.0' __VERSION__ = '1.4.4'
try: try:
xrange # Python 2 xrange # Python 2
@ -74,7 +69,7 @@ except NameError:
_USAGE = """ _USAGE = """
Syntax: cpplint.py [--verbose=#] [--output=emacs|eclipse|vs7|junit|sed|gsed] Syntax: cpplint.py [--verbose=#] [--output=emacs|eclipse|vs7|junit]
[--filter=-x,+y,...] [--filter=-x,+y,...]
[--counting=total|toplevel|detailed] [--root=subdir] [--counting=total|toplevel|detailed] [--root=subdir]
[--repository=path] [--repository=path]
@ -82,8 +77,6 @@ Syntax: cpplint.py [--verbose=#] [--output=emacs|eclipse|vs7|junit|sed|gsed]
[--recursive] [--recursive]
[--exclude=path] [--exclude=path]
[--extensions=hpp,cpp,...] [--extensions=hpp,cpp,...]
[--includeorder=default|standardcfirst]
[--config=filename]
[--quiet] [--quiet]
[--version] [--version]
<file> [file] ... <file> [file] ...
@ -109,16 +102,11 @@ Syntax: cpplint.py [--verbose=#] [--output=emacs|eclipse|vs7|junit|sed|gsed]
Flags: Flags:
output=emacs|eclipse|vs7|junit|sed|gsed output=emacs|eclipse|vs7|junit
By default, the output is formatted to ease emacs parsing. Visual Studio By default, the output is formatted to ease emacs parsing. Visual Studio
compatible output (vs7) may also be used. Further support exists for compatible output (vs7) may also be used. Further support exists for
eclipse (eclipse), and JUnit (junit). XML parsers such as those used eclipse (eclipse), and JUnit (junit). XML parsers such as those used
in Jenkins and Bamboo may also be used. in Jenkins and Bamboo may also be used. Other formats are unsupported.
The sed format outputs sed commands that should fix some of the errors.
Note that this requires gnu sed. If that is installed as gsed on your
system (common e.g. on macOS with homebrew) you can use the gsed output
format. Sed commands are written to stdout, not stderr, so you should be
able to pipe output straight to a shell to run the fixes.
verbose=# verbose=#
Specify a number 0-5 to restrict errors to certain verbosity levels. Specify a number 0-5 to restrict errors to certain verbosity levels.
@ -133,11 +121,11 @@ Syntax: cpplint.py [--verbose=#] [--output=emacs|eclipse|vs7|junit|sed|gsed]
error messages whose category names pass the filters will be printed. error messages whose category names pass the filters will be printed.
(Category names are printed with the message and look like (Category names are printed with the message and look like
"[whitespace/indent]".) Filters are evaluated left to right. "[whitespace/indent]".) Filters are evaluated left to right.
"-FOO" means "do not print categories that start with FOO". "-FOO" and "FOO" means "do not print categories that start with FOO".
"+FOO" means "do print categories that start with FOO". "+FOO" means "do print categories that start with FOO".
Examples: --filter=-whitespace,+whitespace/braces Examples: --filter=-whitespace,+whitespace/braces
--filter=-whitespace,-runtime/printf,+runtime/printf_format --filter=whitespace,runtime/printf,+runtime/printf_format
--filter=-,+build/include_what_you_use --filter=-,+build/include_what_you_use
To see a list of all the categories used in cpplint, pass no arg: To see a list of all the categories used in cpplint, pass no arg:
@ -221,18 +209,6 @@ Syntax: cpplint.py [--verbose=#] [--output=emacs|eclipse|vs7|junit|sed|gsed]
Examples: Examples:
--extensions=%s --extensions=%s
includeorder=default|standardcfirst
For the build/include_order rule, the default is to blindly assume angle
bracket includes with file extension are c-system-headers (default),
even knowing this will have false classifications.
The default is established at google.
standardcfirst means to instead use an allow-list of known c headers and
treat all others as separate group of "other system headers". The C headers
included are those of the C-standard lib and closely related ones.
config=filename
Search for config files with the specified name instead of CPPLINT.cfg
headers=x,y,... headers=x,y,...
The header extensions that cpplint will treat as .h in checks. Values are The header extensions that cpplint will treat as .h in checks. Values are
automatically added to --extensions list. automatically added to --extensions list.
@ -306,7 +282,6 @@ _ERROR_CATEGORIES = [
'build/include_alpha', 'build/include_alpha',
'build/include_order', 'build/include_order',
'build/include_what_you_use', 'build/include_what_you_use',
'build/namespaces_headers',
'build/namespaces_literals', 'build/namespaces_literals',
'build/namespaces', 'build/namespaces',
'build/printf_format', 'build/printf_format',
@ -363,13 +338,6 @@ _ERROR_CATEGORIES = [
'whitespace/todo', 'whitespace/todo',
] ]
# keywords to use with --outputs which generate stdout for machine processing
_MACHINE_OUTPUTS = [
'junit',
'sed',
'gsed'
]
# These error categories are no longer enforced by cpplint, but for backwards- # These error categories are no longer enforced by cpplint, but for backwards-
# compatibility they may still appear in NOLINT comments. # compatibility they may still appear in NOLINT comments.
_LEGACY_ERROR_CATEGORIES = [ _LEGACY_ERROR_CATEGORIES = [
@ -546,186 +514,6 @@ _CPP_HEADERS = frozenset([
'cwctype', 'cwctype',
]) ])
# C headers
_C_HEADERS = frozenset([
# System C headers
'assert.h',
'complex.h',
'ctype.h',
'errno.h',
'fenv.h',
'float.h',
'inttypes.h',
'iso646.h',
'limits.h',
'locale.h',
'math.h',
'setjmp.h',
'signal.h',
'stdalign.h',
'stdarg.h',
'stdatomic.h',
'stdbool.h',
'stddef.h',
'stdint.h',
'stdio.h',
'stdlib.h',
'stdnoreturn.h',
'string.h',
'tgmath.h',
'threads.h',
'time.h',
'uchar.h',
'wchar.h',
'wctype.h',
# additional POSIX C headers
'aio.h',
'arpa/inet.h',
'cpio.h',
'dirent.h',
'dlfcn.h',
'fcntl.h',
'fmtmsg.h',
'fnmatch.h',
'ftw.h',
'glob.h',
'grp.h',
'iconv.h',
'langinfo.h',
'libgen.h',
'monetary.h',
'mqueue.h',
'ndbm.h',
'net/if.h',
'netdb.h',
'netinet/in.h',
'netinet/tcp.h',
'nl_types.h',
'poll.h',
'pthread.h',
'pwd.h',
'regex.h',
'sched.h',
'search.h',
'semaphore.h',
'setjmp.h',
'signal.h',
'spawn.h',
'strings.h',
'stropts.h',
'syslog.h',
'tar.h',
'termios.h',
'trace.h',
'ulimit.h',
'unistd.h',
'utime.h',
'utmpx.h',
'wordexp.h',
# additional GNUlib headers
'a.out.h',
'aliases.h',
'alloca.h',
'ar.h',
'argp.h',
'argz.h',
'byteswap.h',
'crypt.h',
'endian.h',
'envz.h',
'err.h',
'error.h',
'execinfo.h',
'fpu_control.h',
'fstab.h',
'fts.h',
'getopt.h',
'gshadow.h',
'ieee754.h',
'ifaddrs.h',
'libintl.h',
'mcheck.h',
'mntent.h',
'obstack.h',
'paths.h',
'printf.h',
'pty.h',
'resolv.h',
'shadow.h',
'sysexits.h',
'ttyent.h',
# Additional linux glibc headers
'dlfcn.h',
'elf.h',
'features.h',
'gconv.h',
'gnu-versions.h',
'lastlog.h',
'libio.h',
'link.h',
'malloc.h',
'memory.h',
'netash/ash.h',
'netatalk/at.h',
'netax25/ax25.h',
'neteconet/ec.h',
'netipx/ipx.h',
'netiucv/iucv.h',
'netpacket/packet.h',
'netrom/netrom.h',
'netrose/rose.h',
'nfs/nfs.h',
'nl_types.h',
'nss.h',
're_comp.h',
'regexp.h',
'sched.h',
'sgtty.h',
'stab.h',
'stdc-predef.h',
'stdio_ext.h',
'syscall.h',
'termio.h',
'thread_db.h',
'ucontext.h',
'ustat.h',
'utmp.h',
'values.h',
'wait.h',
'xlocale.h',
# Hardware specific headers
'arm_neon.h',
'emmintrin.h',
'xmmintin.h',
])
# Folders of C libraries so commonly used in C++,
# that they have parity with standard C libraries.
C_STANDARD_HEADER_FOLDERS = frozenset([
# standard C library
"sys",
# glibc for linux
"arpa",
"asm-generic",
"bits",
"gnu",
"net",
"netinet",
"protocols",
"rpc",
"rpcsvc",
"scsi",
# linux kernel header
"drm",
"linux",
"misc",
"mtd",
"rdma",
"sound",
"video",
"xen",
])
# Type names # Type names
_TYPES = re.compile( _TYPES = re.compile(
r'^(?:' r'^(?:'
@ -812,10 +600,9 @@ _ALT_TOKEN_REPLACEMENT_PATTERN = re.compile(
# _IncludeState.CheckNextIncludeOrder(). # _IncludeState.CheckNextIncludeOrder().
_C_SYS_HEADER = 1 _C_SYS_HEADER = 1
_CPP_SYS_HEADER = 2 _CPP_SYS_HEADER = 2
_OTHER_SYS_HEADER = 3 _LIKELY_MY_HEADER = 3
_LIKELY_MY_HEADER = 4 _POSSIBLE_MY_HEADER = 4
_POSSIBLE_MY_HEADER = 5 _OTHER_HEADER = 5
_OTHER_HEADER = 6
# These constants define the current inline assembly state # These constants define the current inline assembly state
_NO_ASM = 0 # Outside of inline assembly block _NO_ASM = 0 # Outside of inline assembly block
@ -835,22 +622,6 @@ _SEARCH_C_FILE = re.compile(r'\b(?:LINT_C_FILE|'
# Match string that indicates we're working on a Linux Kernel file. # Match string that indicates we're working on a Linux Kernel file.
_SEARCH_KERNEL_FILE = re.compile(r'\b(?:LINT_KERNEL_FILE)') _SEARCH_KERNEL_FILE = re.compile(r'\b(?:LINT_KERNEL_FILE)')
# Commands for sed to fix the problem
_SED_FIXUPS = {
'Remove spaces around =': r's/ = /=/',
'Remove spaces around !=': r's/ != /!=/',
'Remove space before ( in if (': r's/if (/if(/',
'Remove space before ( in for (': r's/for (/for(/',
'Remove space before ( in while (': r's/while (/while(/',
'Remove space before ( in switch (': r's/switch (/switch(/',
'Should have a space between // and comment': r's/\/\//\/\/ /',
'Missing space before {': r's/\([^ ]\){/\1 {/',
'Tab found, replace by spaces': r's/\t/ /g',
'Line ends in whitespace. Consider deleting these extra spaces.': r's/\s*$//',
'You don\'t need a ; after a }': r's/};/}/',
'Missing space after ,': r's/,\([^ ]\)/, \1/g',
}
_regexp_compile_cache = {} _regexp_compile_cache = {}
# {str, set(int)}: a map from error categories to sets of linenumbers # {str, set(int)}: a map from error categories to sets of linenumbers
@ -870,19 +641,13 @@ _repository = None
# Files to exclude from linting. This is set by the --exclude flag. # Files to exclude from linting. This is set by the --exclude flag.
_excludes = None _excludes = None
# Whether to supress all PrintInfo messages, UNRELATED to --quiet flag # Whether to supress PrintInfo messages
_quiet = False _quiet = False
# The allowed line length of files. # The allowed line length of files.
# This is set by --linelength flag. # This is set by --linelength flag.
_line_length = 80 _line_length = 80
# This allows to use different include order rule than default
_include_order = "default"
# This allows different config files to be used
_config_filename = "CPPLINT.cfg"
try: try:
unicode unicode
except NameError: except NameError:
@ -913,7 +678,7 @@ def unicode_escape_decode(x):
# Treat all headers starting with 'h' equally: .h, .hpp, .hxx etc. # Treat all headers starting with 'h' equally: .h, .hpp, .hxx etc.
# This is set by --headers flag. # This is set by --headers flag.
_hpp_headers = set([]) _hpp_headers = set(['h', 'hh', 'hpp', 'hxx', 'h++', 'cuh'])
# {str, bool}: a map from error categories to booleans which indicate if the # {str, bool}: a map from error categories to booleans which indicate if the
# category should be suppressed for every line. # category should be suppressed for every line.
@ -922,48 +687,30 @@ _global_error_suppressions = {}
def ProcessHppHeadersOption(val): def ProcessHppHeadersOption(val):
global _hpp_headers global _hpp_headers
try: try:
_hpp_headers = {ext.strip() for ext in val.split(',')} _hpp_headers = set(val.split(','))
# Automatically append to extensions list so it does not have to be set 2 times
_valid_extensions.update(_hpp_headers)
except ValueError: except ValueError:
PrintUsage('Header extensions must be comma separated list.') PrintUsage('Header extensions must be comma separated list.')
def ProcessIncludeOrderOption(val):
if val is None or val == "default":
pass
elif val == "standardcfirst":
global _include_order
_include_order = val
else:
PrintUsage('Invalid includeorder value %s. Expected default|standardcfirst')
def IsHeaderExtension(file_extension): def IsHeaderExtension(file_extension):
return file_extension in GetHeaderExtensions() return file_extension in _hpp_headers
def GetHeaderExtensions(): def GetHeaderExtensions():
if _hpp_headers: return _hpp_headers or ['h']
return _hpp_headers
if _valid_extensions:
return {h for h in _valid_extensions if 'h' in h}
return set(['h', 'hh', 'hpp', 'hxx', 'h++', 'cuh'])
# The allowed extensions for file names # The allowed extensions for file names
# This is set by --extensions flag # This is set by --extensions flag
def GetAllExtensions(): def GetAllExtensions():
return GetHeaderExtensions().union(_valid_extensions or set( if not _valid_extensions:
['c', 'cc', 'cpp', 'cxx', 'c++', 'cu'])) return GetHeaderExtensions().union(set(['c', 'cc', 'cpp', 'cxx', 'c++', 'cu']))
return _valid_extensions
def ProcessExtensionsOption(val):
global _valid_extensions
try:
extensions = [ext.strip() for ext in val.split(',')]
_valid_extensions = set(extensions)
except ValueError:
PrintUsage('Extensions should be a comma-separated list of values;'
'for example: extensions=hpp,cpp\n'
'This could not be parsed: "%s"' % (val,))
def GetNonHeaderExtensions(): def GetNonHeaderExtensions():
return GetAllExtensions().difference(GetHeaderExtensions()) return GetAllExtensions().difference(GetHeaderExtensions())
def ParseNolintSuppressions(filename, raw_line, linenum, error): def ParseNolintSuppressions(filename, raw_line, linenum, error):
"""Updates the global list of line error-suppressions. """Updates the global list of line error-suppressions.
@ -1096,13 +843,11 @@ class _IncludeState(object):
_MY_H_SECTION = 1 _MY_H_SECTION = 1
_C_SECTION = 2 _C_SECTION = 2
_CPP_SECTION = 3 _CPP_SECTION = 3
_OTHER_SYS_SECTION = 4 _OTHER_H_SECTION = 4
_OTHER_H_SECTION = 5
_TYPE_NAMES = { _TYPE_NAMES = {
_C_SYS_HEADER: 'C system header', _C_SYS_HEADER: 'C system header',
_CPP_SYS_HEADER: 'C++ system header', _CPP_SYS_HEADER: 'C++ system header',
_OTHER_SYS_HEADER: 'other system header',
_LIKELY_MY_HEADER: 'header this file implements', _LIKELY_MY_HEADER: 'header this file implements',
_POSSIBLE_MY_HEADER: 'header this file may implement', _POSSIBLE_MY_HEADER: 'header this file may implement',
_OTHER_HEADER: 'other header', _OTHER_HEADER: 'other header',
@ -1112,7 +857,6 @@ class _IncludeState(object):
_MY_H_SECTION: 'a header this file implements', _MY_H_SECTION: 'a header this file implements',
_C_SECTION: 'C system header', _C_SECTION: 'C system header',
_CPP_SECTION: 'C++ system header', _CPP_SECTION: 'C++ system header',
_OTHER_SYS_SECTION: 'other system header',
_OTHER_H_SECTION: 'other header', _OTHER_H_SECTION: 'other header',
} }
@ -1226,12 +970,6 @@ class _IncludeState(object):
else: else:
self._last_header = '' self._last_header = ''
return error_message return error_message
elif header_type == _OTHER_SYS_HEADER:
if self._section <= self._OTHER_SYS_SECTION:
self._section = self._OTHER_SYS_SECTION
else:
self._last_header = ''
return error_message
elif header_type == _LIKELY_MY_HEADER: elif header_type == _LIKELY_MY_HEADER:
if self._section <= self._MY_H_SECTION: if self._section <= self._MY_H_SECTION:
self._section = self._MY_H_SECTION self._section = self._MY_H_SECTION
@ -1273,8 +1011,6 @@ class _CppLintState(object):
# "eclipse" - format that eclipse can parse # "eclipse" - format that eclipse can parse
# "vs7" - format that Microsoft Visual Studio 7 can parse # "vs7" - format that Microsoft Visual Studio 7 can parse
# "junit" - format that Jenkins, Bamboo, etc can parse # "junit" - format that Jenkins, Bamboo, etc can parse
# "sed" - returns a gnu sed command to fix the problem
# "gsed" - like sed, but names the command gsed, e.g. for macOS homebrew users
self.output_format = 'emacs' self.output_format = 'emacs'
# For JUnit output, save errors and failures until the end so that they # For JUnit output, save errors and failures until the end so that they
@ -1363,9 +1099,7 @@ class _CppLintState(object):
self.PrintInfo('Total errors found: %d\n' % self.error_count) self.PrintInfo('Total errors found: %d\n' % self.error_count)
def PrintInfo(self, message): def PrintInfo(self, message):
# _quiet does not represent --quiet flag. if not _quiet and self.output_format != 'junit':
# Hide infos from stdout to keep stdout pure for machine consumption
if not _quiet and self.output_format not in _MACHINE_OUTPUTS:
sys.stdout.write(message) sys.stdout.write(message)
def PrintError(self, message): def PrintError(self, message):
@ -1383,9 +1117,9 @@ class _CppLintState(object):
num_failures = len(self._junit_failures) num_failures = len(self._junit_failures)
testsuite = xml.etree.ElementTree.Element('testsuite') testsuite = xml.etree.ElementTree.Element('testsuite')
testsuite.attrib['name'] = 'cpplint'
testsuite.attrib['errors'] = str(num_errors) testsuite.attrib['errors'] = str(num_errors)
testsuite.attrib['failures'] = str(num_failures) testsuite.attrib['failures'] = str(num_failures)
testsuite.attrib['name'] = 'cpplint'
if num_errors == 0 and num_failures == 0: if num_errors == 0 and num_failures == 0:
testsuite.attrib['tests'] = str(1) testsuite.attrib['tests'] = str(1)
@ -1579,7 +1313,7 @@ class FileInfo(object):
If we have a real absolute path name here we can try to do something smart: If we have a real absolute path name here we can try to do something smart:
detecting the root of the checkout and truncating /path/to/checkout from detecting the root of the checkout and truncating /path/to/checkout from
the name so that we get header guards that don't include things like the name so that we get header guards that don't include things like
"C:\\Documents and Settings\\..." or "/home/username/..." in them and thus "C:\Documents and Settings\..." or "/home/username/..." in them and thus
people on different computers who have checked the source out to different people on different computers who have checked the source out to different
locations won't see bogus errors. locations won't see bogus errors.
""" """
@ -1725,13 +1459,6 @@ def Error(filename, linenum, category, confidence, message):
elif _cpplint_state.output_format == 'junit': elif _cpplint_state.output_format == 'junit':
_cpplint_state.AddJUnitFailure(filename, linenum, message, category, _cpplint_state.AddJUnitFailure(filename, linenum, message, category,
confidence) confidence)
elif _cpplint_state.output_format in ['sed', 'gsed']:
if message in _SED_FIXUPS:
sys.stdout.write(_cpplint_state.output_format + " -i '%s%s' %s # %s [%s] [%d]\n" % (
linenum, _SED_FIXUPS[message], filename, message, category, confidence))
else:
sys.stderr.write('# %s:%s: "%s" [%s] [%d]\n' % (
filename, linenum, message, category, confidence))
else: else:
final_message = '%s:%s: %s [%s] [%d]\n' % ( final_message = '%s:%s: %s [%s] [%d]\n' % (
filename, linenum, message, category, confidence) filename, linenum, message, category, confidence)
@ -1872,7 +1599,7 @@ def FindNextMultiLineCommentEnd(lines, lineix):
def RemoveMultiLineCommentsFromRange(lines, begin, end): def RemoveMultiLineCommentsFromRange(lines, begin, end):
"""Clears a range of lines for multi-line comments.""" """Clears a range of lines for multi-line comments."""
# Having // <empty> comments makes the lines non-empty, so we will not get # Having // dummy comments makes the lines non-empty, so we will not get
# unnecessary blank line warnings later in the code. # unnecessary blank line warnings later in the code.
for i in range(begin, end): for i in range(begin, end):
lines[i] = '/**/' lines[i] = '/**/'
@ -1927,7 +1654,6 @@ class CleansedLines(object):
self.raw_lines = lines self.raw_lines = lines
self.num_lines = len(lines) self.num_lines = len(lines)
self.lines_without_raw_strings = CleanseRawStrings(lines) self.lines_without_raw_strings = CleanseRawStrings(lines)
# # pylint: disable=consider-using-enumerate
for linenum in range(len(self.lines_without_raw_strings)): for linenum in range(len(self.lines_without_raw_strings)):
self.lines.append(CleanseComments( self.lines.append(CleanseComments(
self.lines_without_raw_strings[linenum])) self.lines_without_raw_strings[linenum]))
@ -2247,7 +1973,7 @@ def CheckForCopyright(filename, lines, error):
"""Logs an error if no Copyright message appears at the top of the file.""" """Logs an error if no Copyright message appears at the top of the file."""
# We'll say it should occur by line 10. Don't forget there's a # We'll say it should occur by line 10. Don't forget there's a
# placeholder line at the front. # dummy line at the front.
for line in xrange(1, min(len(lines), 11)): for line in xrange(1, min(len(lines), 11)):
if re.search(r'Copyright', lines[line], re.I): break if re.search(r'Copyright', lines[line], re.I): break
else: # means no copyright line was found else: # means no copyright line was found
@ -2350,8 +2076,7 @@ def GetHeaderGuardCPPVariable(filename):
# --root=.. , will prepend the outer directory to the header guard # --root=.. , will prepend the outer directory to the header guard
full_path = fileinfo.FullName() full_path = fileinfo.FullName()
# adapt slashes for windows root_abspath = os.path.abspath(_root)
root_abspath = os.path.abspath(_root).replace('\\', '/')
maybe_path = StripListPrefix(PathSplitToList(full_path), maybe_path = StripListPrefix(PathSplitToList(full_path),
PathSplitToList(root_abspath)) PathSplitToList(root_abspath))
@ -2491,22 +2216,16 @@ def CheckHeaderFileIncluded(filename, include_state, error):
continue continue
headername = FileInfo(headerfile).RepositoryName() headername = FileInfo(headerfile).RepositoryName()
first_include = None first_include = None
include_uses_unix_dir_aliases = False
for section_list in include_state.include_list: for section_list in include_state.include_list:
for f in section_list: for f in section_list:
include_text = f[0] if headername in f[0] or f[0] in headername:
if "./" in include_text:
include_uses_unix_dir_aliases = True
if headername in include_text or include_text in headername:
return return
if not first_include: if not first_include:
first_include = f[1] first_include = f[1]
message = '%s should include its header file %s' % (fileinfo.RepositoryName(), headername) error(filename, first_include, 'build/include', 5,
if include_uses_unix_dir_aliases: '%s should include its header file %s' % (fileinfo.RepositoryName(),
message += ". Relative paths like . and .. are not allowed." headername))
error(filename, first_include, 'build/include', 5, message)
def CheckForBadCharacters(filename, lines, error): def CheckForBadCharacters(filename, lines, error):
@ -3157,7 +2876,7 @@ class NestingState(object):
# }; # };
class_decl_match = Match( class_decl_match = Match(
r'^(\s*(?:template\s*<[\w\s<>,:=]*>\s*)?' r'^(\s*(?:template\s*<[\w\s<>,:=]*>\s*)?'
r'(class|struct)\s+(?:[a-zA-Z0-9_]+\s+)*(\w+(?:::\w+)*))' r'(class|struct)\s+(?:[A-Z_]+\s+)*(\w+(?:::\w+)*))'
r'(.*)$', line) r'(.*)$', line)
if (class_decl_match and if (class_decl_match and
(not self.stack or self.stack[-1].open_parentheses == 0)): (not self.stack or self.stack[-1].open_parentheses == 0)):
@ -3425,8 +3144,7 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum,
Search(r'\bstd\s*::\s*initializer_list\b', constructor_args[0])) Search(r'\bstd\s*::\s*initializer_list\b', constructor_args[0]))
copy_constructor = bool( copy_constructor = bool(
onearg_constructor and onearg_constructor and
Match(r'((const\s+(volatile\s+)?)?|(volatile\s+(const\s+)?))?' Match(r'(const\s+)?%s(\s*<[^>]*>)?(\s+const)?\s*(?:<\w+>\s*)?&'
r'%s(\s*<[^>]*>)?(\s+const)?\s*(?:<\w+>\s*)?&'
% re.escape(base_classname), constructor_args[0].strip())) % re.escape(base_classname), constructor_args[0].strip()))
if (not is_marked_explicit and if (not is_marked_explicit and
@ -3485,7 +3203,7 @@ def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error):
# Note that we assume the contents of [] to be short enough that # Note that we assume the contents of [] to be short enough that
# they'll never need to wrap. # they'll never need to wrap.
if ( # Ignore control structures. if ( # Ignore control structures.
not Search(r'\b(if|elif|for|while|switch|return|new|delete|catch|sizeof)\b', not Search(r'\b(if|for|while|switch|return|new|delete|catch|sizeof)\b',
fncall) and fncall) and
# Ignore pointers/references to functions. # Ignore pointers/references to functions.
not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and
@ -3598,7 +3316,7 @@ def CheckForFunctionLengths(filename, clean_lines, linenum,
if Search(r'(;|})', start_line): # Declarations and trivial functions if Search(r'(;|})', start_line): # Declarations and trivial functions
body_found = True body_found = True
break # ... ignore break # ... ignore
if Search(r'{', start_line): elif Search(r'{', start_line):
body_found = True body_found = True
function = Search(r'((\w|:)*)\(', line).group(1) function = Search(r'((\w|:)*)\(', line).group(1)
if Match(r'TEST', function): # Handle TEST... macros if Match(r'TEST', function): # Handle TEST... macros
@ -3791,10 +3509,9 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
# get rid of comments and strings # get rid of comments and strings
line = clean_lines.elided[linenum] line = clean_lines.elided[linenum]
# You shouldn't have spaces before your brackets, except for C++11 attributes # You shouldn't have spaces before your brackets, except maybe after
# or maybe after 'delete []', 'return []() {};', or 'auto [abc, ...] = ...;'. # 'delete []' or 'return []() {};'
if (Search(r'\w\s+\[(?!\[)', line) and if Search(r'\w\s+\[', line) and not Search(r'(?:delete|return)\s+\[', line):
not Search(r'(?:auto&?|delete|return)\s+\[', line)):
error(filename, linenum, 'whitespace/braces', 5, error(filename, linenum, 'whitespace/braces', 5,
'Extra space before [') 'Extra space before [')
@ -4312,11 +4029,11 @@ def CheckBraces(filename, clean_lines, linenum, error):
# its line, and the line after that should have an indent level equal to or # its line, and the line after that should have an indent level equal to or
# lower than the if. We also check for ambiguous if/else nesting without # lower than the if. We also check for ambiguous if/else nesting without
# braces. # braces.
if_else_match = Search(r'\b(if\s*(|constexpr)\s*\(|else\b)', line) if_else_match = Search(r'\b(if\s*\(|else\b)', line)
if if_else_match and not Match(r'\s*#', line): if if_else_match and not Match(r'\s*#', line):
if_indent = GetIndentLevel(line) if_indent = GetIndentLevel(line)
endline, endlinenum, endpos = line, linenum, if_else_match.end() endline, endlinenum, endpos = line, linenum, if_else_match.end()
if_match = Search(r'\bif\s*(|constexpr)\s*\(', line) if_match = Search(r'\bif\s*\(', line)
if if_match: if if_match:
# This could be a multiline if condition, so find the end first. # This could be a multiline if condition, so find the end first.
pos = if_match.end() - 1 pos = if_match.end() - 1
@ -4375,9 +4092,9 @@ def CheckTrailingSemicolon(filename, clean_lines, linenum, error):
# Block bodies should not be followed by a semicolon. Due to C++11 # Block bodies should not be followed by a semicolon. Due to C++11
# brace initialization, there are more places where semicolons are # brace initialization, there are more places where semicolons are
# required than not, so we explicitly list the allowed rules rather # required than not, so we use a whitelist approach to check these
# than listing the disallowed ones. These are the places where "};" # rather than a blacklist. These are the places where "};" should
# should be replaced by just "}": # be replaced by just "}":
# 1. Some flavor of block following closing parenthesis: # 1. Some flavor of block following closing parenthesis:
# for (;;) {}; # for (;;) {};
# while (...) {}; # while (...) {};
@ -4433,11 +4150,11 @@ def CheckTrailingSemicolon(filename, clean_lines, linenum, error):
# - INTERFACE_DEF # - INTERFACE_DEF
# - EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED, LOCKS_EXCLUDED: # - EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED, LOCKS_EXCLUDED:
# #
# We implement a list of safe macros instead of a list of # We implement a whitelist of safe macros instead of a blacklist of
# unsafe macros, even though the latter appears less frequently in # unsafe macros, even though the latter appears less frequently in
# google code and would have been easier to implement. This is because # google code and would have been easier to implement. This is because
# the downside for getting the allowed checks wrong means some extra # the downside for getting the whitelist wrong means some extra
# semicolons, while the downside for getting disallowed checks wrong # semicolons, while the downside for getting the blacklist wrong
# would result in compile errors. # would result in compile errors.
# #
# In addition to macros, we also don't want to warn on # In addition to macros, we also don't want to warn on
@ -4858,7 +4575,7 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state,
# if(match($0, " <<")) complain = 0; # if(match($0, " <<")) complain = 0;
# if(match(prev, " +for \\(")) complain = 0; # if(match(prev, " +for \\(")) complain = 0;
# if(prevodd && match(prevprev, " +for \\(")) complain = 0; # if(prevodd && match(prevprev, " +for \\(")) complain = 0;
scope_or_label_pattern = r'\s*(?:public|private|protected|signals)(?:\s+(?:slots\s*)?)?:\s*\\?$' scope_or_label_pattern = r'\s*\w+\s*:\s*\\?$'
classinfo = nesting_state.InnermostClass() classinfo = nesting_state.InnermostClass()
initial_spaces = 0 initial_spaces = 0
cleansed_line = clean_lines.elided[linenum] cleansed_line = clean_lines.elided[linenum]
@ -4982,14 +4699,13 @@ def _DropCommonSuffixes(filename):
return os.path.splitext(filename)[0] return os.path.splitext(filename)[0]
def _ClassifyInclude(fileinfo, include, used_angle_brackets, include_order="default"): def _ClassifyInclude(fileinfo, include, is_system):
"""Figures out what kind of header 'include' is. """Figures out what kind of header 'include' is.
Args: Args:
fileinfo: The current file cpplint is running over. A FileInfo instance. fileinfo: The current file cpplint is running over. A FileInfo instance.
include: The path to a #included file. include: The path to a #included file.
used_angle_brackets: True if the #include used <> rather than "". is_system: True if the #include used <> rather than "".
include_order: "default" or other value allowed in program arguments
Returns: Returns:
One of the _XXX_HEADER constants. One of the _XXX_HEADER constants.
@ -4999,8 +4715,6 @@ def _ClassifyInclude(fileinfo, include, used_angle_brackets, include_order="defa
_C_SYS_HEADER _C_SYS_HEADER
>>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'string', True) >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'string', True)
_CPP_SYS_HEADER _CPP_SYS_HEADER
>>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', True, "standardcfirst")
_OTHER_SYS_HEADER
>>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', False) >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', False)
_LIKELY_MY_HEADER _LIKELY_MY_HEADER
>>> _ClassifyInclude(FileInfo('foo/foo_unknown_extension.cc'), >>> _ClassifyInclude(FileInfo('foo/foo_unknown_extension.cc'),
@ -5011,23 +4725,17 @@ def _ClassifyInclude(fileinfo, include, used_angle_brackets, include_order="defa
""" """
# This is a list of all standard c++ header files, except # This is a list of all standard c++ header files, except
# those already checked for above. # those already checked for above.
is_cpp_header = include in _CPP_HEADERS is_cpp_h = include in _CPP_HEADERS
# Mark include as C header if in list or in a known folder for standard-ish C headers.
is_std_c_header = (include_order == "default") or (include in _C_HEADERS
# additional linux glibc header folders
or Search(r'(?:%s)\/.*\.h' % "|".join(C_STANDARD_HEADER_FOLDERS), include))
# Headers with C++ extensions shouldn't be considered C system headers # Headers with C++ extensions shouldn't be considered C system headers
is_system = used_angle_brackets and not os.path.splitext(include)[1] in ['.hpp', '.hxx', '.h++'] if is_system and os.path.splitext(include)[1] in ['.hpp', '.hxx', '.h++']:
is_system = False
if is_system: if is_system:
if is_cpp_header: if is_cpp_h:
return _CPP_SYS_HEADER return _CPP_SYS_HEADER
if is_std_c_header:
return _C_SYS_HEADER
else: else:
return _OTHER_SYS_HEADER return _C_SYS_HEADER
# If the target file and the include we're checking share a # If the target file and the include we're checking share a
# basename when we drop common extensions, and the include # basename when we drop common extensions, and the include
@ -5081,12 +4789,10 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error):
# #
# We also make an exception for Lua headers, which follow google # We also make an exception for Lua headers, which follow google
# naming convention but not the include convention. # naming convention but not the include convention.
match = Match(r'#include\s*"([^/]+\.(.*))"', line) match = Match(r'#include\s*"([^/]+\.h)"', line)
if match: if match and not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1)):
if (IsHeaderExtension(match.group(2)) and
not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1))):
error(filename, linenum, 'build/include_subdir', 4, error(filename, linenum, 'build/include_subdir', 4,
'Include the directory when naming header files') 'Include the directory when naming .h files')
# we shouldn't include a file more than once. actually, there are a # we shouldn't include a file more than once. actually, there are a
# handful of instances where doing so is okay, but in general it's # handful of instances where doing so is okay, but in general it's
@ -5094,7 +4800,7 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error):
match = _RE_PATTERN_INCLUDE.search(line) match = _RE_PATTERN_INCLUDE.search(line)
if match: if match:
include = match.group(2) include = match.group(2)
used_angle_brackets = (match.group(1) == '<') is_system = (match.group(1) == '<')
duplicate_line = include_state.FindHeader(include) duplicate_line = include_state.FindHeader(include)
if duplicate_line >= 0: if duplicate_line >= 0:
error(filename, linenum, 'build/include', 4, error(filename, linenum, 'build/include', 4,
@ -5109,19 +4815,7 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error):
'Do not include .' + extension + ' files from other packages') 'Do not include .' + extension + ' files from other packages')
return return
# We DO want to include a 3rd party looking header if it matches the if not _THIRD_PARTY_HEADERS_PATTERN.match(include):
# filename. Otherwise we get an erroneous error "...should include its
# header" error later.
third_src_header = False
for ext in GetHeaderExtensions():
basefilename = filename[0:len(filename) - len(fileinfo.Extension())]
headerfile = basefilename + '.' + ext
headername = FileInfo(headerfile).RepositoryName()
if headername in include or include in headername:
third_src_header = True
break
if third_src_header or not _THIRD_PARTY_HEADERS_PATTERN.match(include):
include_state.include_list[-1].append((include, linenum)) include_state.include_list[-1].append((include, linenum))
# We want to ensure that headers appear in the right order: # We want to ensure that headers appear in the right order:
@ -5136,7 +4830,7 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error):
# track of the highest type seen, and complains if we see a # track of the highest type seen, and complains if we see a
# lower type after that. # lower type after that.
error_message = include_state.CheckNextIncludeOrder( error_message = include_state.CheckNextIncludeOrder(
_ClassifyInclude(fileinfo, include, used_angle_brackets, _include_order)) _ClassifyInclude(fileinfo, include, is_system))
if error_message: if error_message:
error(filename, linenum, 'build/include_order', 4, error(filename, linenum, 'build/include_order', 4,
'%s. Should be: %s.h, c system, c++ system, other.' % '%s. Should be: %s.h, c system, c++ system, other.' %
@ -5390,7 +5084,7 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension,
if (IsHeaderExtension(file_extension) if (IsHeaderExtension(file_extension)
and Search(r'\bnamespace\s*{', line) and Search(r'\bnamespace\s*{', line)
and line[-1] != '\\'): and line[-1] != '\\'):
error(filename, linenum, 'build/namespaces_headers', 4, error(filename, linenum, 'build/namespaces', 4,
'Do not use unnamed namespaces in header files. See ' 'Do not use unnamed namespaces in header files. See '
'https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces' 'https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces'
' for more information.') ' for more information.')
@ -5680,19 +5374,19 @@ def CheckForNonConstReference(filename, clean_lines, linenum,
# #
# We also accept & in static_assert, which looks like a function but # We also accept & in static_assert, which looks like a function but
# it's actually a declaration expression. # it's actually a declaration expression.
allowed_functions = (r'(?:[sS]wap(?:<\w:+>)?|' whitelisted_functions = (r'(?:[sS]wap(?:<\w:+>)?|'
r'operator\s*[<>][<>]|' r'operator\s*[<>][<>]|'
r'static_assert|COMPILE_ASSERT' r'static_assert|COMPILE_ASSERT'
r')\s*\(') r')\s*\(')
if Search(allowed_functions, line): if Search(whitelisted_functions, line):
return return
elif not Search(r'\S+\([^)]*$', line): elif not Search(r'\S+\([^)]*$', line):
# Don't see an allowed function on this line. Actually we # Don't see a whitelisted function on this line. Actually we
# didn't see any function name on this line, so this is likely a # didn't see any function name on this line, so this is likely a
# multi-line parameter list. Try a bit harder to catch this case. # multi-line parameter list. Try a bit harder to catch this case.
for i in xrange(2): for i in xrange(2):
if (linenum > i and if (linenum > i and
Search(allowed_functions, clean_lines.elided[linenum - i - 1])): Search(whitelisted_functions, clean_lines.elided[linenum - i - 1])):
return return
decls = ReplaceAll(r'{[^}]*}', ' ', line) # exclude function body decls = ReplaceAll(r'{[^}]*}', ' ', line) # exclude function body
@ -5767,7 +5461,7 @@ def CheckCasts(filename, clean_lines, linenum, error):
if not expecting_function: if not expecting_function:
CheckCStyleCast(filename, clean_lines, linenum, 'static_cast', CheckCStyleCast(filename, clean_lines, linenum, 'static_cast',
r'\((int|float|double|bool|char|u?int(16|32|64)|size_t)\)', error) r'\((int|float|double|bool|char|u?int(16|32|64))\)', error)
# This doesn't catch all cases. Consider (const char * const)"hello". # This doesn't catch all cases. Consider (const char * const)"hello".
# #
@ -5859,8 +5553,7 @@ def CheckCStyleCast(filename, clean_lines, linenum, cast_type, pattern, error):
return False return False
# operator++(int) and operator--(int) # operator++(int) and operator--(int)
if (context.endswith(' operator++') or context.endswith(' operator--') or if context.endswith(' operator++') or context.endswith(' operator--'):
context.endswith('::operator++') or context.endswith('::operator--')):
return False return False
# A single unnamed argument for a function tends to look like old style cast. # A single unnamed argument for a function tends to look like old style cast.
@ -5921,11 +5614,11 @@ _HEADERS_CONTAINING_TEMPLATES = (
)), )),
('<limits>', ('numeric_limits',)), ('<limits>', ('numeric_limits',)),
('<list>', ('list',)), ('<list>', ('list',)),
('<map>', ('multimap',)), ('<map>', ('map', 'multimap',)),
('<memory>', ('allocator', 'make_shared', 'make_unique', 'shared_ptr', ('<memory>', ('allocator', 'make_shared', 'make_unique', 'shared_ptr',
'unique_ptr', 'weak_ptr')), 'unique_ptr', 'weak_ptr')),
('<queue>', ('queue', 'priority_queue',)), ('<queue>', ('queue', 'priority_queue',)),
('<set>', ('multiset',)), ('<set>', ('set', 'multiset',)),
('<stack>', ('stack',)), ('<stack>', ('stack',)),
('<string>', ('char_traits', 'basic_string',)), ('<string>', ('char_traits', 'basic_string',)),
('<tuple>', ('tuple',)), ('<tuple>', ('tuple',)),
@ -5959,16 +5652,6 @@ for _header, _templates in _HEADERS_MAYBE_TEMPLATES:
(re.compile(r'[^>.]\b' + _template + r'(<.*?>)?\([^\)]'), (re.compile(r'[^>.]\b' + _template + r'(<.*?>)?\([^\)]'),
_template, _template,
_header)) _header))
# Match set<type>, but not foo->set<type>, foo.set<type>
_re_pattern_headers_maybe_templates.append(
(re.compile(r'[^>.]\bset\s*\<'),
'set<>',
'<set>'))
# Match 'map<type> var' and 'std::map<type>(...)', but not 'map<type>(...)''
_re_pattern_headers_maybe_templates.append(
(re.compile(r'(std\b::\bmap\s*\<)|(^(std\b::\b)map\b\(\s*\<)'),
'map<>',
'<map>'))
# Other scripts may reach in and modify this pattern. # Other scripts may reach in and modify this pattern.
_re_pattern_templates = [] _re_pattern_templates = []
@ -6051,7 +5734,9 @@ def UpdateIncludeState(filename, include_dict, io=codecs):
""" """
headerfile = None headerfile = None
try: try:
with io.open(filename, 'r', 'utf8', 'replace') as headerfile: headerfile = io.open(filename, 'r', 'utf8', 'replace')
except IOError:
return False
linenum = 0 linenum = 0
for line in headerfile: for line in headerfile:
linenum += 1 linenum += 1
@ -6061,9 +5746,6 @@ def UpdateIncludeState(filename, include_dict, io=codecs):
include = match.group(2) include = match.group(2)
include_dict.setdefault(include, linenum) include_dict.setdefault(include, linenum)
return True return True
except IOError:
return False
def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error,
@ -6533,13 +6215,13 @@ def ProcessConfigOverrides(filename):
if not base_name: if not base_name:
break # Reached the root directory. break # Reached the root directory.
cfg_file = os.path.join(abs_path, _config_filename) cfg_file = os.path.join(abs_path, "CPPLINT.cfg")
abs_filename = abs_path abs_filename = abs_path
if not os.path.isfile(cfg_file): if not os.path.isfile(cfg_file):
continue continue
try: try:
with open(cfg_file, encoding='utf-8') as file_handle: with open(cfg_file) as file_handle:
for line in file_handle: for line in file_handle:
line, _, _ = line.partition('#') # Remove comments. line, _, _ = line.partition('#') # Remove comments.
if not line.strip(): if not line.strip():
@ -6577,15 +6259,20 @@ def ProcessConfigOverrides(filename):
except ValueError: except ValueError:
_cpplint_state.PrintError('Line length must be numeric.') _cpplint_state.PrintError('Line length must be numeric.')
elif name == 'extensions': elif name == 'extensions':
ProcessExtensionsOption(val) global _valid_extensions
try:
extensions = [ext.strip() for ext in val.split(',')]
_valid_extensions = set(extensions)
except ValueError:
sys.stderr.write('Extensions should be a comma-separated list of values;'
'for example: extensions=hpp,cpp\n'
'This could not be parsed: "%s"' % (val,))
elif name == 'root': elif name == 'root':
global _root global _root
# root directories are specified relative to CPPLINT.cfg dir. # root directories are specified relative to CPPLINT.cfg dir.
_root = os.path.join(os.path.dirname(cfg_file), val) _root = os.path.join(os.path.dirname(cfg_file), val)
elif name == 'headers': elif name == 'headers':
ProcessHppHeadersOption(val) ProcessHppHeadersOption(val)
elif name == 'includeorder':
ProcessIncludeOrderOption(val)
else: else:
_cpplint_state.PrintError( _cpplint_state.PrintError(
'Invalid configuration option (%s) in file %s\n' % 'Invalid configuration option (%s) in file %s\n' %
@ -6642,8 +6329,7 @@ def ProcessFile(filename, vlevel, extra_check_functions=None):
codecs.getwriter('utf8'), codecs.getwriter('utf8'),
'replace').read().split('\n') 'replace').read().split('\n')
else: else:
with codecs.open(filename, 'r', 'utf8', 'replace') as target_file: lines = codecs.open(filename, 'r', 'utf8', 'replace').read().split('\n')
lines = target_file.read().split('\n')
# Remove trailing '\r'. # Remove trailing '\r'.
# The -1 accounts for the extra trailing blank line we get from split() # The -1 accounts for the extra trailing blank line we get from split()
@ -6703,10 +6389,10 @@ def PrintUsage(message):
Args: Args:
message: The optional error message. message: The optional error message.
""" """
sys.stderr.write(_USAGE % (sorted(list(GetAllExtensions())), sys.stderr.write(_USAGE % (list(GetAllExtensions()),
','.join(sorted(list(GetAllExtensions()))), ','.join(list(GetAllExtensions())),
sorted(GetHeaderExtensions()), GetHeaderExtensions(),
','.join(sorted(GetHeaderExtensions())))) ','.join(GetHeaderExtensions())))
if message: if message:
sys.exit('\nFATAL ERROR: ' + message) sys.exit('\nFATAL ERROR: ' + message)
@ -6752,8 +6438,6 @@ def ParseArguments(args):
'exclude=', 'exclude=',
'recursive', 'recursive',
'headers=', 'headers=',
'includeorder=',
'config=',
'quiet']) 'quiet'])
except getopt.GetoptError: except getopt.GetoptError:
PrintUsage('Invalid arguments.') PrintUsage('Invalid arguments.')
@ -6771,9 +6455,9 @@ def ParseArguments(args):
if opt == '--version': if opt == '--version':
PrintVersion() PrintVersion()
elif opt == '--output': elif opt == '--output':
if val not in ('emacs', 'vs7', 'eclipse', 'junit', 'sed', 'gsed'): if val not in ('emacs', 'vs7', 'eclipse', 'junit'):
PrintUsage('The only allowed output formats are emacs, vs7, eclipse ' PrintUsage('The only allowed output formats are emacs, vs7, eclipse '
'sed, gsed and junit.') 'and junit.')
output_format = val output_format = val
elif opt == '--quiet': elif opt == '--quiet':
quiet = True quiet = True
@ -6805,16 +6489,15 @@ def ParseArguments(args):
_excludes = set() _excludes = set()
_excludes.update(glob.glob(val)) _excludes.update(glob.glob(val))
elif opt == '--extensions': elif opt == '--extensions':
ProcessExtensionsOption(val) global _valid_extensions
try:
_valid_extensions = set(val.split(','))
except ValueError:
PrintUsage('Extensions must be comma seperated list.')
elif opt == '--headers': elif opt == '--headers':
ProcessHppHeadersOption(val) ProcessHppHeadersOption(val)
elif opt == '--recursive': elif opt == '--recursive':
recursive = True recursive = True
elif opt == '--includeorder':
ProcessIncludeOrderOption(val)
elif opt == '--config':
global _config_filename
_config_filename = val
if not filenames: if not filenames:
PrintUsage('No files were specified.') PrintUsage('No files were specified.')
@ -6831,7 +6514,6 @@ def ParseArguments(args):
_SetFilters(filters) _SetFilters(filters)
_SetCountingStyle(counting_style) _SetCountingStyle(counting_style)
filenames.sort()
return filenames return filenames
def _ExpandDirectories(filenames): def _ExpandDirectories(filenames):
@ -6863,35 +6545,15 @@ def _ExpandDirectories(filenames):
for filename in expanded: for filename in expanded:
if os.path.splitext(filename)[1][1:] in GetAllExtensions(): if os.path.splitext(filename)[1][1:] in GetAllExtensions():
filtered.append(filename) filtered.append(filename)
return filtered return filtered
def _FilterExcludedFiles(fnames): def _FilterExcludedFiles(filenames):
"""Filters out files listed in the --exclude command line switch. File paths """Filters out files listed in the --exclude command line switch. File paths
in the switch are evaluated relative to the current working directory in the switch are evaluated relative to the current working directory
""" """
exclude_paths = [os.path.abspath(f) for f in _excludes] exclude_paths = [os.path.abspath(f) for f in _excludes]
# because globbing does not work recursively, exclude all subpath of all excluded entries return [f for f in filenames if os.path.abspath(f) not in exclude_paths]
return [f for f in fnames
if not any(e for e in exclude_paths
if _IsParentOrSame(e, os.path.abspath(f)))]
def _IsParentOrSame(parent, child):
"""Return true if child is subdirectory of parent.
Assumes both paths are absolute and don't contain symlinks.
"""
parent = os.path.normpath(parent)
child = os.path.normpath(child)
if parent == child:
return True
prefix = os.path.commonprefix([parent, child])
if prefix != parent:
return False
# Note: os.path.commonprefix operates on character basis, so
# take extra care of situations like '/foo/ba' and '/foo/bar/baz'
child_suffix = child[len(prefix):]
child_suffix = child_suffix.lstrip(os.sep)
return child == os.path.join(prefix, child_suffix)
def main(): def main():
filenames = ParseArguments(sys.argv[1:]) filenames = ParseArguments(sys.argv[1:])

@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
export EXAMPLE="$1" export EXAMPLE=$1
EXNAME=$(echo "${EXAMPLE}" |cut -c 3-) EXNAME=$(echo "${EXAMPLE}" |cut -c 3-)
cd examples ||exit; \ cd examples ||exit; \
install -d dirname "${EXAMPLE}" install -d dirname "${EXAMPLE}"

@ -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,738 @@
#!/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
}
deprecation_message() {
echo "kaleidoscope-builder is deprecated and will be removed"
echo "by February 15, 2021"
echo ""
echo "To switch to the new build system, replace your sketch's"
echo "Makefile with a copy of:"
echo ""
echo "${KALEIDOSCOPE_DIR}/etc/Makefile.sketch"
echo ""
}
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
}
KALEIDOSCOPE_DIR="$(cd "$(dirname "$0")"/..; pwd)"
# shellcheck disable=SC2034
KALEIDOSCOPE_BIN_DIR="${KALEIDOSCOPE_DIR}/bin/"
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
deprecation_message
# 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

@ -19,28 +19,13 @@ else
ARDUINO_LOCAL_LIB_PATH="${ARDUINO_LOCAL_LIB_PATH:-${HOME}/Arduino}" ARDUINO_LOCAL_LIB_PATH="${ARDUINO_LOCAL_LIB_PATH:-${HOME}/Arduino}"
fi fi
echo "Preparing Kaleidoscope and the bundle..."
if [ ! -d ./.arduino/user/hardware ]; then
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 .arduino/data \
--exclude .arduino/downloads \
--exclude .arduino/user/hardware/keyboardio/avr/libraries/Kaleidoscope \
--exclude .arduino/user/hardware/keyboardio/gd32/libraries/Kaleidoscope \
--exclude bin/arduino-cli \ --exclude bin/arduino-cli \
--exclude .docker_xfer \ --exclude .docker_xfer \
--exclude .git \ --exclude .git \
@ -48,29 +33,22 @@ tar -cf "${XFER_DIR}/kaleidoscope.tar" \
--exclude testing/googletest/build \ --exclude testing/googletest/build \
. .
fi echo "The bundle is coming from ${ARDUINO_DIRECTORIES_USER}/hardware/keyboardio"
(cd "${ARDUINO_DIRECTORIES_USER}/hardware/keyboardio" && tar -cf "${XFER_DIR}/bundle.tar" \
if [ -z "$DOCKER_LIVE_KALEIDOSCOPE_DIR" ]; then --exclude .git --exclude avr/libraries/Kaleidoscope .)
_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 # We do want word splitting since there are multiple options here
# shellcheck disable=SC2086 # shellcheck disable=SC2086
docker run --rm $DOCKER_RUN_INTERACTIVE_OPTS \ docker run --rm $DOCKER_RUN_INTERACTIVE_OPTS \
${_KALEIDOSCOPE_MOUNT} \ --mount type=tmpfs,destination=/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/testing/googletest/build,consistency=delegated \
--mount type=volume,source=kaleidoscope-build,destination=/kaleidoscope/_build,consistency=delegated \ --mount type=volume,source=kaleidoscope-build,destination=/kaleidoscope/_build,consistency=delegated \
--env ARDUINO_DIRECTORIES_DATA=/arduino-cli/data \ --env ARDUINO_DIRECTORIES_DATA=/arduino-cli/data \
--env ARDUINO_DIRECTORIES_USER=/kaleidoscope/.arduino/user/ \ --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 {} \;

@ -18,11 +18,6 @@ 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: examples:
install -d examples install -d examples

@ -12,108 +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 ### Better protection against unintended modifiers from Qukeys
Qukeys has two new configuration options for preventing unintended modifiers in Qukeys has two new configuration options for preventing unintended modifiers in
@ -188,23 +86,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 +132,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 +197,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,16 @@ 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 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,107 +33,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 ### 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. 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.
@ -156,7 +47,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 +100,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 +159,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 +234,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 +294,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 +323,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 +455,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 +503,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 +523,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 +539,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 +556,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 +649,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

@ -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,6 @@ extensions = [
] ]
extensions.append('copy-examples') extensions.append('copy-examples')
extensions.append('copy-plugin-readmes')
# Setup the breathe extension # Setup the breathe extension
@ -100,8 +99,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.

@ -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

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,7 +89,7 @@ method explained below.
## Dependencies ## Dependencies
* [Kaleidoscope-Ranges](Kaleidoscope-Ranges.md) * [Kaleidoscope-Ranges](Ranges.md)
## Further reading ## Further reading

@ -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,23 +25,25 @@ 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

@ -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,7 +114,7 @@ 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

@ -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,8 +61,8 @@ 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

@ -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,14 +172,9 @@ 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

@ -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,12 +47,12 @@ 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

@ -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.

@ -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`.

@ -79,7 +79,7 @@ and properties:
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)
## Further reading ## Further reading

@ -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,7 +74,7 @@ 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

@ -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)`

@ -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,12 +108,12 @@ 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

@ -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,7 +43,7 @@ method:
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)
## Further reading ## Further reading

@ -2,8 +2,9 @@
With this plugin, any active modifier on the keyboard will have the LED under it 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, highlighted. No matter how the modifier got activated (a key press, a macro,
anything else), the coloring will apply. Layer shift keys and OneShot layer keys anything else), the coloring will apply. Layer keys, be them layer toggles,
count as modifiers as far as the plugin is concerned. momentary switches, or one-shot layer keys count as modifiers as far as the
plugin is concerned.
## Using the plugin ## Using the plugin
@ -32,26 +33,15 @@ with the LEDs, and apply the highlight over those.
## Plugin properties ## Plugin properties
The plugin provides the `ActiveModColorEffect` object, which has the following The plugin provides the `ActiveModColorEffect` object, which has the following
configuration methods. These methods all take a `cRGB` object, which can be properties:
written as `CRGB(r, g, b)`, where `r`, `g`, and `b` are all 8-bit integers
(0-255). For example, `CRGB(50, 0, 50)` would be a purple-ish color.
### `.setHighlightColor(color)` ### `.highlight_color`
> Sets the color (a `cRGB` object) to use for highlighting normal modifier keys > The color to use for highlighting the modifiers. Defaults to a white color.
> and layer-shift keys. Defaults to a white color.
### `.setOneShotColor(color)` ### `.sticky_color`
> Sets the color (a `cRGB` object) to use for highlighting active one-shot > The color to use for highlighting one-shot modifiers when they are sticky. Defaults to a red color.
> keys. These are the keys that will time out or deactivate when a subsequent
> key is pressed. Defaults to a yellow color.
### `.setStickyColor(color)`
> Sets the color (a `cRGB` object) to use for highlighting "sticky" one-shot
> keys. These keys will remain active until they are pressed again. Defaults to
> a red color.
## Plugin methods ## Plugin methods
@ -63,13 +53,8 @@ The `ActiveModColorEffect` object provides the following methods:
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)
* [Kaleidoscope-OneShot](Kaleidoscope-OneShot.md) * [Kaleidoscope-OneShot](OneShot.md)
* [Kaleidoscope-OneShotMetaKeys](Kaleidoscope-OneShotMetaKeys.md)
The `ActiveModColorEffect` plugin doesn't require that either OneShot or
OneShotMetaKeys plugins are registered with `KALEIDOSCOPE_INIT_PLUGINS()` in
order to work, but it does depend on their header files.
## Further reading ## Further reading

@ -92,7 +92,7 @@ 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

@ -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,9 +116,9 @@ 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

@ -80,7 +80,7 @@ The plugin provides the following effects:
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)
## Further reading ## Further reading

@ -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)

@ -15,7 +15,7 @@ them.
KALEIDOSCOPE_INIT_PLUGINS(LEDControl, JukeBoxEffect); KALEIDOSCOPE_INIT_PLUGINS(LEDControl, JukeBoxEffect);
void setup() { void setup(void) {
Kaleidoscope.setup(); Kaleidoscope.setup();
} }
``` ```
@ -49,39 +49,6 @@ light green, except for the `Esc` key, which will be in red.
An alternative color scheme exists under the `JukeboxAlternateEffect` name, An alternative color scheme exists under the `JukeboxAlternateEffect` name,
where the light green and red colors are swapped. where the light green and red colors are swapped.
### TriColor
TriColor is a class that can be used to create LED effects that all follow a similar
pattern: alphas and similar in one color; modifiers, special keys, and half the
function keys in another, and `Esc` in a third (this latter being optional). If
we have a color scheme that follows this pattern, the `TriColor` extension can
make it a lot easier to implement it.
## Using the extension
Because the extension is part of the `LEDEffects` library,
we need to include that header:
```c++
#include <Kaleidoscope-LEDEffects.h>
```
Then, we simply create a new instance of the `TriColor` class, with appropriate
colors set for the constructor:
```c++
kaleidoscope::plugin::TriColor BlackAndWhiteEffect (CRGB(0x00, 0x00, 0x00),
CRGB(0xff, 0xff, 0xff),
CRGB(0x80, 0x80, 0x80));
```
The first argument is the base color, the second is for modifiers and special
keys, the last one is for the `Esc` key. If the last one is omitted, the
extension will use the modifier color for it.
## Plugin methods ## Plugin methods
The plugin provides a single method on each of the included effect objects: The plugin provides a single method on each of the included effect objects:
@ -94,7 +61,7 @@ The plugin provides a single method on each of the included effect objects:
## Dependencies ## Dependencies
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](LEDControl.md)
## Further reading ## Further reading

@ -3,7 +3,7 @@
The `LayerFocus` plugin exposes a number of layer-related commands via The `LayerFocus` plugin exposes a number of layer-related commands via
[Focus][plugin:focus], to allow controlling layers from the host side. [Focus][plugin:focus], to allow controlling layers from the host side.
[plugin:focus]: Kaleidoscope-FocusSerial.md [plugin:focus]: FocusSerial.md
## Using the plugin ## Using the plugin
@ -43,4 +43,4 @@ The plugin provides the following Focus commands:
## Dependencies ## Dependencies
* [Kaleidoscope-FocusSerial](Kaleidoscope-FocusSerial.md) * [Kaleidoscope-FocusSerial](FocusSerial.md)

@ -58,6 +58,9 @@ The dictionary is made up of a list of keys, and an action callback. Using the
`LEADER_DICT` and `LEADER_SEQ` helpers is recommended. The dictionary *must* be `LEADER_DICT` and `LEADER_SEQ` helpers is recommended. The dictionary *must* be
marked `PROGMEM`! marked `PROGMEM`!
**Note** that we need to use the `Leader` object before any other that adds or
changes key behaviour! Failing to do so may result in unpredictable behaviour.
## Plugin methods ## Plugin methods
The plugin provides the `Leader` object, with the following methods and properties: The plugin provides the `Leader` object, with the following methods and properties:
@ -77,7 +80,7 @@ The plugin provides the `Leader` object, with the following methods and properti
> are final actions, where one does not wish to continue the leader sequence > are final actions, where one does not wish to continue the leader sequence
> further in the hopes of finding a longer match. > further in the hopes of finding a longer match.
### `.setTimeout(ms)` ### `.time_out`
> The number of milliseconds to wait before a sequence times out. Once the > The number of milliseconds to wait before a sequence times out. Once the
> sequence timed out, if there is a partial match with an action, that will be > sequence timed out, if there is a partial match with an action, that will be
@ -87,7 +90,7 @@ The plugin provides the `Leader` object, with the following methods and properti
## Dependencies ## Dependencies
* [Kaleidoscope-Ranges](Kaleidoscope-Ranges.md) * [Kaleidoscope-Ranges](Ranges.md)
## Further reading ## Further reading

@ -14,10 +14,10 @@ In Kaleidoscope, macros are implemented via this plugin. You can define upto 256
## Using the plugin ## Using the plugin
To use the plugin, we need to include the header, initialize the plugins with To use the plugin, we need to include the header, tell the firmware to `use` the
`KALEIDOSCOPE_INIT_PLUGINS()`, place macros on the keymap, and create a special plugin, place macros on the keymap, and create a special handler function
handler function (`macroAction()`) that will determine what happens when macro (`macroAction`) that will tell the plugin what shall happen when macro keys are
keys are pressed. It is best illustrated with an example: pressed. It is best illustrated with an example:
```c++ ```c++
#include <Kaleidoscope.h> #include <Kaleidoscope.h>
@ -34,24 +34,21 @@ enum {
M(MACRO_MODEL01), M(MACRO_HELLO), M(MACRO_SPECIAL) M(MACRO_MODEL01), M(MACRO_HELLO), M(MACRO_SPECIAL)
// later in the Sketch: // later in the Sketch:
const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) {
switch (macro_id) { switch (macroIndex) {
case MACRO_MODEL01: case MACRO_MODEL01:
if (keyToggledOn(event.state)) { return MACRODOWN(I(25),
return MACRO(I(25),
D(LeftShift), T(M), U(LeftShift), T(O), T(D), T(E), T(L), D(LeftShift), T(M), U(LeftShift), T(O), T(D), T(E), T(L),
T(Spacebar), T(Spacebar),
W(100), W(100),
T(0), T(1) ); T(0), T(1) );
}
break;
case MACRO_HELLO: case MACRO_HELLO:
if (keyToggledOn(event.state)) { if (keyToggledOn(keyState)) {
return Macros.type(PSTR("Hello "), PSTR("world!")); return Macros.type(PSTR("Hello "), PSTR("world!"));
} }
break; break;
case MACRO_SPECIAL: case MACRO_SPECIAL:
if (keyToggledOn(event.state)) { if (keyToggledOn(keyState)) {
// Do something special // Do something special
} }
break; break;
@ -104,34 +101,22 @@ The plugin provides a `Macros` object, with the following methods and properties
> easiest way to do that is to wrap the string in a `PSTR()` helper. See the > easiest way to do that is to wrap the string in a `PSTR()` helper. See the
> program code at the beginning of this documentation for an example! > program code at the beginning of this documentation for an example!
### `.press(key)`/`.release(key)` ### `.row`, `.col`
> Used in `Macros.play()`, these methods press virtual keys in a small > The `row` and `col` properties describe the physical position a macro was
> supplemental `Key` array for the purpose of keeping keys active for complex > triggered from if it was triggered by a key. The playback functions
> macro sequences where it's important to have overlapping key presses. > do not use these properties, but they are available, would one want to create
> a macro that needs to know which key triggered it.
> >
> `Macros.press(key)` sends a key press event, and will keep that virtual key > When the macro was not triggered by a key the value of these properties are
> active until either `Macros.release(key)` is called, or a Macros key is > unspecified.
> released. If you use `Macros.press(key)` in a macro, but also change the value
> of `event.key`, you will need to make sure to also call `Macros.release(key)`
> at some point to prevent that key from getting "stuck" on.
### `.clear()`
> Releases all virtual keys held by macros. This both empties the supplemental
> `Key` array (see above) and sends a release event for each key stored there.
### `.tap(key)`
> Sends an immediate press and release event for `key` with no delay, using an
> invalid key address.
## Macro helpers ## Macro helpers
Macros need to be able to simulate key down and key up events for any key - even Macros need to be able to simulate key down and key up events for any key - even
keys that may not be on the keymap otherwise. For this reason and others, we keys that may not be on the keymap otherwise. For this reason and others, we
need to define them in a special way, using the `MACRO` helper. need to define them in a special way, using the `MACRO` helper (or its
`MACRODOWN()` variant, see below):
### `MACRO(steps...)` ### `MACRO(steps...)`
@ -143,6 +128,20 @@ need to define them in a special way, using the `MACRO` helper.
> that end with END will still work correctly, but new code should not use END; > that end with END will still work correctly, but new code should not use END;
> usage of END is deprecated. > usage of END is deprecated.
### `MACRODOWN(steps...)`
> The same as the `MACRO()` helper above, but it will create a special sequence,
> where the steps are only played back when the triggering key was just pressed.
> That is, the macro will not be performed when the key is released, or held, or
> not pressed at all.
>
> Use this over `MACRO()` when you only want to perform an action when the key
> actuates, and no action should be taken when it is held, released, or when it
> is not pressed at all. For a lot of macros that emit a sequence without any
> other side effects, `MACRODOWN()` is usually the better choice.
>
> Can only be used from the `macroAction()` overrideable method.
## `MACRO` steps ## `MACRO` steps
Macro steps can be divided into the following groups: Macro steps can be divided into the following groups:
@ -165,11 +164,7 @@ In most cases, one is likely use normal keys for the steps, so the `D`, `U`, and
`T` steps apply the `Key_` prefix. This allows us to write `MACRO(T(X))` instead `T` steps apply the `Key_` prefix. This allows us to write `MACRO(T(X))` instead
of `MACRO(Tr(Key_X))` - making the macro definition shorter, and more readable. of `MACRO(Tr(Key_X))` - making the macro definition shorter, and more readable.
The "raw" variants (`Dr`/`Ur`/`Tr`) use the full name of the `Key` object, The compact variant (`Dc`, `Uc`, and `Tc`) prefix the argument with `Key_` too,
without adding the `Key_` prefix to the argument given. `Tr(Key_X)` is the same
as `T(X)`.
The "compact" variants (`Dc`/`Uc`/`Tc`) prefix the argument with `Key_` too,
but unlike `D`, `U`, and `T`, they ignore the `flags` component of the key, and but unlike `D`, `U`, and `T`, they ignore the `flags` component of the key, and
as such, are limited to ordinary keys. Mouse keys, consumer- or system keys are as such, are limited to ordinary keys. Mouse keys, consumer- or system keys are
not supported by this compact representation. not supported by this compact representation.
@ -193,9 +188,26 @@ them in order.
with `Key_`, and they ignore the `flags` component of a key, and as such, are with `Key_`, and they ignore the `flags` component of a key, and as such, are
limited to ordinary keys. limited to ordinary keys.
## Overrideable functions ### Controlling when to send reports
While the plugin will - by default - send a report after every step, that is not
always desirable. For this reason, we allow turning this implicit reporting off,
and switching to explicit reporting instead. Note that the tap steps (`T()`,
`Tr()`, and `Tc()`) will always send an implicit report, and so will
`Macros.type()`.
To control when to send reports, the following steps can be used:
* `WITH_EXPLICIT_REPORT`: Prevents the plugin from sending an implicit report
after every step. To send a report, one needs to have a `SEND_REPORT` step
too.
* `WITH_IMPLICIT_REPORT`: Enables sending an implicit report after every step
(the default).
* `SEND_REPORT`: Send a report.
### `macroAction(uint8_t macro_id, KeyEvent &event)` ## Overrideable methods
### `macroAction(macroIndex, keyState)`
> The `macroAction` method is the brain of the macro support in Kaleidoscope: > The `macroAction` method is the brain of the macro support in Kaleidoscope:
> this function tells the plugin what sequence to play when given a macro index > this function tells the plugin what sequence to play when given a macro index
@ -203,14 +215,3 @@ them in order.
> >
> It should return a macro sequence, or `MACRO_NONE` if nothing is to be played > It should return a macro sequence, or `MACRO_NONE` if nothing is to be played
> back. > back.
## Limitations
Due to technical and practical reasons, `Macros.type()` assumes a QWERTY layout
on the host side, and so do all other parts that work with keycodes. If your
operating system is set to a different layout, the strings and keycodes will
need to be adjusted accordingly.
## Dependencies
* [Kaleidoscope-MacroSupport](Kaleidoscope-MacroSupport.md)

@ -42,9 +42,9 @@ that are part of a combination.
## Plugin properties ## Plugin properties
The extension provides a `MagicCombo` singleton object, with the following The extension provides a `MagicCombo` singleton object, with the following
method: property:
### `.setMinInterval(min_interval)` ### `.min_interval`
> Restrict the magic action to fire at most once every `min_interval` > Restrict the magic action to fire at most once every `min_interval`
> milliseconds. > milliseconds.

@ -37,23 +37,21 @@ void setup() {
The plugin provides a number of keys one can put on the keymap, that allow The plugin provides a number of keys one can put on the keymap, that allow
control of the mouse. They can be divided into a few groups: control of the mouse. They can be divided into a few groups:
### Mouse buttons
Mouse button keys are straightforward; pressing one is the same as pressing the
corresponding button on a physical mouse. You can hold a mouse button key to
perform drag gestures, as you might expect. MouseKeys supports five mouse
buttons: left, right, middle, previous, and next.
* `Key_mouseBtnL`, `Key_mouseBtnM`, `Key_mouseBtnR`, `Key_mouseBtnP`,
`Key_mouseBtnN`: The left, middle, right, previous, and next mouse buttons,
respectively.
### Cursor movement ### Cursor movement
When a cursor movement key is pressed, the mouse cursor will begin to move The simplest set of keys are the mouse cursor movement keys. These move the
slowly, then accelerate to full speed. Both the full speed and the time it cursor one direction or the other, with speed and acceleration factored in. When
takes to reach full speed are configurable. a mouse cursor movement key is held down, it will move `.speed` pixels each
`.speedDelay` milliseconds without acceleration. But when `.accelSpeed` is
non-zero (and it is not zero by default,
see [below](#accelspeed-and-acceldelay)), the speed will increase by
`.accelSpeed` every `.accelDelay` milliseconds. Thus, unless configured
otherwise, holding a direction will move that way at increasing speed.
One can hold more than one key down at the same time, and the cursor will move
towards a direction that is the combination of the keys held. For example,
holding the "mouse up" and "mouse right" keys together will move the cursor
diagonally up and right.
The cursor movement keys are as follows: The cursor movement keys are as follows:
@ -62,18 +60,26 @@ The cursor movement keys are as follows:
* `Key_mouseUpL`, `Key_mouseUpR`, `Key_mouseDnL`, `Key_mouseDnR`: Move the * `Key_mouseUpL`, `Key_mouseUpR`, `Key_mouseDnL`, `Key_mouseDnR`: Move the
cursor up-left, up-right, down-left, down-right, respectively. cursor up-left, up-right, down-left, down-right, respectively.
### Scroll wheels ### Scroll wheel
Controlling the scroll wheel is similarly simple. It does not have Controlling the scroll wheel is similarly simple. It does not have acceleration,
acceleration, but one can control the speed with the but one can control the speed with the `.wheelSpeed` and `.wheelDelay`
`MouseKeys.setScrollInterval()` function, which controls the length of time properties (see below).
between scroll events.
* `Key_mouseScrollUp`, `Key_mouseScrollDn`: Scroll the mouse wheel up or down, * `Key_mouseScrollUp`, `Key_mouseScrollDn`: Scroll the mouse wheel up or down,
respectively. respectively.
* `Key_mouseScrollL`, `Key_mouseScrollR`: Scroll the mouse wheel left or right, * `Key_mouseScrollL`, `Key_mouseScrollR`: Scroll the mouse wheel left or right,
respectively. respectively.
### Buttons
Buttons are even simpler than movement: there is no movement speed, nor
acceleration involved. One just presses them.
* `Key_mouseBtnL`, `Key_mouseBtnM`, `Key_mouseBtnR`, `Key_mouseBtnP`,
`Key_mouseBtnN`: The left, middle, right, previous, and next mouse buttons,
respectively.
## Warping ## Warping
Warping is one of the most interesting features of the plugin, and is a feature Warping is one of the most interesting features of the plugin, and is a feature
@ -175,12 +181,12 @@ mapping is shown below on the left side of a keyboard with a QWERTY layout:
---|---|--- W - Warp NW Sector (Key_mouseWarpNW) ---|---|--- W - Warp NW Sector (Key_mouseWarpNW)
A S | D | F G E - Warp N Sector (Key_mouseWarpN) A S | D | F G E - Warp N Sector (Key_mouseWarpN)
---|---|--- R - Warp NE Sector (Key_mouseWarpNE) ---|---|--- R - Warp NE Sector (Key_mouseWarpNE)
X | C | V B S - Warp W Sector (Key_mouseWarpW) X | C | V B S - Warp E Sector (Key_mouseWarpE)
D - Warp/Zoom Center (Key_mouseWarpIn) D - Warp/Zoom Center (Key_mouseWarpIn)
F - Warp E Sector (Key_mouseWarpE) F - Warp W Sector (Key_mouseWarpW)
X - Warp SW Sector (Key_mouseWarpSW) K - Warp SE Sector (Key_mouseWarpSE)
C - Warp S Sector (Key_mouseWarpS) C - Warp S Sector (Key_mouseWarpS)
V - Warp SE Sector (Key_mouseWarpSE) V - Warp SW Sector (Key_mouseWarpSW)
T - Right Click (Key_mouseBtnR) T - Right Click (Key_mouseBtnR)
G - Left Click (Key_mouseBtnL) G - Left Click (Key_mouseBtnL)
B - Middle Click (Key_mouseBtnM) B - Middle Click (Key_mouseBtnM)
@ -204,38 +210,37 @@ the following additions:
The plugin provides a `MouseKeys` object, with the following methods and The plugin provides a `MouseKeys` object, with the following methods and
properties available: properties available:
### `.setCursorInitSpeed(speed)`/`.getCursorInitSpeed()` ### `.speed` and `.speedDelay`
> Controls (or returns) the current starting speed value for mouse cursor > These two control the speed of the mouse cursor, when a movement key is held.
> movement. When a mouse movement key is pressed, the cursor starts moving at > The former, `.speed`, controls the amount of pixels the cursor moves, when it
> this speed, then accelerates. The number is abstract, but linear, with higher > has to move, and defaults to 1. The latter, `.speedDelay` is the amount of
> numbers representing faster speeds. Default starting speed is `1`. > time - in milliseconds - to wait between two movements, and defaults to 0, no
> delay.
### `.setCursorBaseSpeed(speed)`/`.getCursorBaseSpeed()` ### `.accelSpeed` and `.accelDelay`
> Controls (or returns) the current top speed value for mouse cursor movement. > These two properties control the speed of acceleration. The former,
> When a mouse movement key is pressed, the cursor accelerates until it reaches > `.accelSpeed`, controls how much the speed shall be increased at each step,
> this speed. The number is abstract, but linear, with higher numbers > while the second, `.accelDelay`, controls how often (in milliseconds)
> representing faster speeds. Default full-speed value is `50`. > acceleration should be applied.
>
> They default to 1 pixel and 50 milliseconds, respectively.
### `.setCursorAccelDuration(duration)`/`.getCursorAccelDuration()` ### `.wheelSpeed` and `.wheelDelay`
> Controls (or returns) the current time it takes for the mouse cursor to reach > The last two properties supported by the plugin control the mouse wheel
> full speed (in milliseconds), starting from when the first movement key is > scrolling speed. The former, `.wheelSpeed`, controls the amount of ticks the
> pressed. Default value is `800` ms. > wheel shall scroll, and defaults to 1. The second, `.wheelDelay`, controls the
> delay between two scroll events, and defaults to 50 milliseconds.
### `.setScrollInterval(interval)`/`.getScrollInterval()` ### `.setSpeedLimit`
> Controls (or returns) the current scrolling speed, by setting the time between > This method sets the maximum speed after which acceleration stops.
> mouse scroll reports (in milliseconds). Default value is `50` ms. > The default is 127, and the minimum value is 16 (things will not work
> properly below 16).
### `.setWarpGridSize(size)` ### `.setWarpGridSize`
> This method changes the size of the grid used for [warping](#warping). The > This method changes the size of the grid used for [warping](#warping). The
> following are valid sizes: `MOUSE_WARP_GRID_2X2`, `MOUSE_WARP_GRID_3X3` > following are valid sizes: `MOUSE_WARP_GRID_2X2`, `MOUSE_WARP_GRID_3X3`
## Further reading
There is an [example][plugin:example] that demonstrates how to use this plugin.
[plugin:example]: /examples/Features/MouseKeys/MouseKeys.ino

@ -0,0 +1,205 @@
# OneShot
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
subject to a time-out.
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.
Furthermore, if a one-shot key is double-tapped ie tapped two times in quick
succession, it becomes sticky, and remains active until disabled with a third tap.
This can be useful when one needs to input a number of keys with the modifier or
layer active, and does not wish to hold the key down. If this "stickability"
feature is undesirable, it can be unset (and later again set) for individual
modifiers/layers. If stickability is unset, double-tapping a one-shot modifier
will just restart the timer.
To make multi-modifier, or multi-layer shortcuts possible, one-shot keys remain
active if another one-shot of the same type is tapped, so `Ctrl, Alt, b` becomes
`Ctrl+Alt+b`, and `L1, L2, c` is turned into `L1+L2+c`. Furthermore, modifiers
and other layer keys do not cancel the one-shot effect, either.
## Using One-Shot Keys
To enter one-shot mode, tap _quickly_ on a one-shot key. The next
normal (non-one-shot) key you press will have the modifier applied,
and then the modifier will automatically turn off. If the Shift key is
a one-shot modifier, then hitting `Shift, a, b` will give you `Ab`,
_if you hit shift quickly._
Longish keypresses do not activate one-shot mode. If you press `Shift,
a, b`, as above, but hold the Shift key a bit longer, you'll get `ab`.
To enter sticky mode, _tap twice quickly_ on a one-shot key. The
modifier will now stay on until you press it again. Continuing the
`Shift` example, tapping `Shift, Shift` _quickly_ and then `a, b, c,
Shift, d, e, f` will give you `ABCdef`.
This can be a bit tricky; combining this plugin with
[LED-ActiveModColor](LED-ActiveModColor.md)
will help you understand what state your one-shot is in; when a
one-shot key is active, it will have a white LED highlight; when
sticky, a red highlight. (These colors are configurable.)
## Using the plugin
After adding one-shot keys to the keymap, all one needs to do, is enable the
plugin:
```c++
#include <Kaleidoscope.h>
#include <Kaleidoscope-OneShot.h>
// somewhere in the keymap...
OSM(LeftControl), OSL(_FN)
KALEIDOSCOPE_INIT_PLUGINS(OneShot);
void setup() {
Kaleidoscope.setup();
}
```
## Keymap markup
There are two macros the plugin provides:
### `OSM(mod)`
> A macro that takes a single argument, the name of the modifier: `LeftControl`,
> `LeftShift`, `LeftAlt`, `LeftGui` or their right-side variant. When marked up
> with this macro, the modifier will act as a one-shot modifier.
### `OSL(layer)`
> Takes a layer number as argument, and sets up the key to act as a one-shot
> layer key.
>
> Please note that while `Kaleidoscope` supports more, one-shot layers are
> limited to 8 layers only.
## Plugin methods
The plugin provides one object, `OneShot`, which implements both one-shot
modifiers and one-shot layer keys. It has the following methods:
### `.isActive()`
> Returns if any one-shot key is in flight. This makes it possible to
> differentiate between having a modifier or layer active, versus having them
> active only until after the next key getting pressed. And this, in turn, is
> useful for macros that need to fiddle with either modifier or layer state: if
> one-shots are not active, they need not restore the original state.
### `.isPressed()`
> Returns true if any one-shot key is still held.
### `.isSticky(key)`
> Returns if the key is currently sticky.
### `.isModifierActive(key)`
> Returns if the modifier `key` has a one-shot state active. Use this together
> with `Kaleidoscope.hid().keyboard().isModifierKeyActive` to catch cases where
> a one-shot modifier is active, but not registered yet.
### `.cancel([with_stickies])`
> The `cancel()` method can be used to cancel any pending one-shot effects,
> useful when one changed their minds, and does not wish to wait for the
> timeout.
>
> The optional `with_stickies` argument, if set to `true`, will also cancel
> sticky one-shot effects. If omitted, it defaults to `false`, and not canceling
> stickies.
### `.inject(key, keyState)`
> Simulates a key event, specifically designed to inject one-shot keys into the
> event loop. The primary purpose of this method is to make it easier to trigger
> multiple one-shots at the same time.
>
> See the example sketch for more information about its use.
### `.enableStickability(key...)`
> Enables stickability for all keys listed. The keys should all be OneShot keys,
> as if specified on the keymap. For example:
> `OneShot.enableStickability(OSM(LeftShift), OSL(1))`.
>
> By default, all oneshot keys are stickable.
### `.enableStickabilityForModifiers()`
### `.enableStickabilityForLayers()`
> Enables stickability for all modifiers and layers, respectively. These are
> convenience methods for cases where one wants to enable stickability for a
> group of one-shot keys.
### `.disableStickability(key...)`
> Disables stickability for all keys listed. The keys should all be OneShot keys,
> as if specified on the keymap. For example:
> `OneShot.disableStickability(OSM(LeftShift), OSL(1))`.
>
> By default, all oneshot keys are stickable.
### `.disableStickabilityForModifiers()`
### `.disableStickabilityForLayers()`
> Disables stickability for all modifiers and layers, respectively. These are
> convenience methods for cases where one wants to disable stickability for a
> group of one-shot keys.
## Plugin properties
Along with the methods listed above, the `OneShot` object has the following
properties too:
### `.time_out`
> Set this property to the number of milliseconds to wait before timing out and
> cancelling the one-shot effect (unless interrupted or cancelled before by any
> other means).
>
> Defaults to 2500.
### `.hold_time_out`
> Set this property to the number of milliseconds to wait before considering a
> held one-shot key as intentionally held. In this case, the one-shot effect
> will not trigger when the key is released. In other words, holding a one-shot
> key at least this long, and then releasing it, will not trigger the one-shot
> effect.
>
> Defaults to 200.
### `.double_tap_time_out`
> Set this property to the number of milliseconds within which a second
> uninterrupted tap of the same one-shot key will be treated as a sticky-tap.
> Only takes effect when `.double_tap_sticky` is set.
>
>
> Setting the property to `-1` will make the double-tap timeout use `.time_out`
> for its calculations.
>
> Defaults to -1.
## Dependencies
* [Kaleidoscope-Ranges](Ranges.md)
## Further reading
Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin.
[plugin:example]: /examples/Keystrokes/OneShot/OneShot.ino

@ -75,22 +75,6 @@ likely to generate errors and out-of-order events.
> >
> Defaults to `250`. > Defaults to `250`.
### `.setMaxIntervalForTapRepeat(timeout)`
> Sets the time (in milliseconds) that limits the tap-repeat window. If the same
> qukey is pressed, released, and pressed again within this timeframe, then
> held, Qukeys will turn it into a single press and hold event, using the
> primary key value (which cannot otherwise be held). If the second press is
> also a tap, and the two _release_ events occur within the same timeframe, it
> will instead be treated as a double tap (of the primary key value).
>
> To effectively shut off the tap-repeat feature, set this value to `0`. The
> maximum value is `255`; anything higher than `250` could result in key repeat
> being triggered on the host before Qukeys determines whether it's a tap-repeat
> or a double-tap sequence, because most systems delay the key repeat by 500 ms.
>
> Defaults to `200`.
### `.setOverlapThreshold(percentage)` ### `.setOverlapThreshold(percentage)`
> This sets a variable that allows the user to roll over from a qukey to a > This sets a variable that allows the user to roll over from a qukey to a
@ -221,16 +205,6 @@ the given alternate value on all layers, regardless of what the primary value is
for that key on the top currently active layer. for that key on the top currently active layer.
### Tap-Repeat Behaviour
If a qukey is tapped, then immediately pressed and held, Qukeys will turn that
sequence of events into a single press and hold of the primary key value
(whereas merely holding the key yeilds the alternate value). This is
particularly useful on macOS apps that use Apple's Cocoa input system, where
holding a key gives the user access to a menu of accented characters, rather
than merely repeating the same character until the key is released.
## Design & Implementation ## Design & Implementation
When a qukey is pressed, it doesn't immediately add a corresponding keycode to When a qukey is pressed, it doesn't immediately add a corresponding keycode to

@ -23,7 +23,7 @@ void setup() {
} }
``` ```
## Overridable plugin methods ## Overrideable plugin methods
### `bool shouldRemember(Key mapped_key)` ### `bool shouldRemember(Key mapped_key)`
@ -37,7 +37,7 @@ void setup() {
## Dependencies ## Dependencies
* [Kaleidoscope-Ranges](Kaleidoscope-Ranges.md) * [Kaleidoscope-Ranges](Ranges.md)
## Further reading ## Further reading

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

Loading…
Cancel
Save