Compare commits
7 Commits
main
...
f/driver/k
Author | SHA1 | Date |
---|---|---|
Jesse Vincent | 290a4db2d4 | 4 years ago |
Gergely Nagy | cacca53496 | 4 years ago |
Gergely Nagy | 35a8170cec | 4 years ago |
Gergely Nagy | b1e2a889bb | 4 years ago |
Gergely Nagy | 35b1fb239c | 4 years ago |
Gergely Nagy | 2c4a274cb3 | 4 years ago |
Gergely Nagy | ada56fe6b9 | 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
|
|
@ -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,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)
|
|
@ -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
|
@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
# This script sets all of the files inside src and example to have mtimes
|
||||||
|
# that match the times of the last git commit that touched each file
|
||||||
|
|
||||||
|
# This can be useful when build tools depend on file timestamps to
|
||||||
|
# make caching decisions
|
||||||
|
|
||||||
|
find src examples -type f -exec sh -c '
|
||||||
|
timestamp=$(git log --pretty=format:%ad --date=format:%Y%m%d%H%M.%S -n 1 HEAD "$1" 2> /dev/null)
|
||||||
|
if [ "x$timestamp" != "x" ]; then
|
||||||
|
touch -t "$timestamp" "$1"
|
||||||
|
fi
|
||||||
|
' sh {} \;
|
||||||
|
|
@ -1,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)
|
|
@ -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.
|
|
@ -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
|
|
||||||
```
|
|
@ -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
|
||||||
|
|
@ -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
|
|
After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 149 KiB |
After Width: | Height: | Size: 281 KiB |
Before Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 239 KiB |
After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 270 KiB After Width: | Height: | Size: 626 KiB |
Before Width: | Height: | Size: 269 KiB After Width: | Height: | Size: 624 KiB |
@ -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
|
@ -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
|
@ -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.
|
@ -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
|