Compare commits
5 Commits
main
...
f/samd-tes
Author | SHA1 | Date |
---|---|---|
Jesse Vincent | 620fbdb6ce | 4 years ago |
Jesse Vincent | 4181085d13 | 4 years ago |
Jesse Vincent | 14cefe2b6d | 4 years ago |
Jesse Vincent | f098c8a81b | 4 years ago |
Jesse Vincent | b25b9b02f4 | 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 {} \;
|
||||||
|
|
Binary file not shown.
@ -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
|
|
||||||
```
|
|
@ -1,458 +0,0 @@
|
|||||||
# How to write a Kaleidoscope plugin
|
|
||||||
|
|
||||||
This is a brief guide intended for those who want to write custom Kaleidoscope plugins. It covers basic things you'll need to know about how Kaleidoscope calls plugin event handlers, and how it will respond to actions taken by those plugins.
|
|
||||||
|
|
||||||
## What can a plugin do?
|
|
||||||
|
|
||||||
There are many things that Kaleidoscope plugins are capable of, from LED effects, serial communication with the host, altering HID reports, and interacting with other plugins. It's useful to break these capabilities down into some broad categories, based on the types of input a plugin can respond to.
|
|
||||||
|
|
||||||
- Key events (key switches toggling on and off)
|
|
||||||
- Focus commands (sent to the keyboard from software on the host via the serial port)
|
|
||||||
- LED updates
|
|
||||||
- Keymap layer changes
|
|
||||||
- Timers
|
|
||||||
|
|
||||||
## An example plugin
|
|
||||||
|
|
||||||
To make a Kaleidoscope plugin, we create a subclass of the `kaleidoscope::Plugin` class, usually in the `kaleidoscope::plugin` namespace:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
namespace kaleidoscope {
|
|
||||||
namespace plugin {
|
|
||||||
|
|
||||||
class MyPlugin : public Plugin {};
|
|
||||||
|
|
||||||
} // namespace kaleidoscope
|
|
||||||
} // namespace plugin
|
|
||||||
```
|
|
||||||
|
|
||||||
This code can be placed in a separate C++ source file, but it's simplest to just define it right in the sketch's \*.ino file for now.
|
|
||||||
|
|
||||||
By convention, we create a singleton object named like the plugin's class in the global namespace. This is typical of Arduino code.
|
|
||||||
|
|
||||||
```c++
|
|
||||||
kaleidoscope::plugin::MyPlugin MyPlugin;
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, in order to connect that plugin to the Kaleidoscope event handler system, we need to register it in the call to the preprocessor macro `KALEIDOSCOPE_INIT_PLUGINS()` in the sketch:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
KALEIDOSCOPE_INIT_PLUGINS(MyPlugin, OtherPlugin);
|
|
||||||
```
|
|
||||||
|
|
||||||
To make our plugin do anything useful, we need to add [[event-handler-hooks]] to it. This is how Kaleidoscope delivers input events to its registered plugins. Here's an example:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
class MyPlugin : public Plugin {
|
|
||||||
public:
|
|
||||||
EventHandlerResult onKeyEvent(KeyEvent &event);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
This will result in `MyPlugin.onKeyEvent()` being called (along with other plugins' `onKeyEvent()` methods) when Kaleidoscope detects a key state change. This function returns one of three `EventHandlerResult` values:
|
|
||||||
|
|
||||||
- `EventHandlerResult::OK` indicates that Kaleidoscope should proceed on to the event handler for the next plugin in the chain.
|
|
||||||
- `EventHandlerResult::ABORT` indicates that Kaleidoscope should stop processing immediately, and treat the event as if it didn't happen.
|
|
||||||
- `EventHandlerResult::EVENT_CONSUMED` stops event processing like `ABORT`, but records that the key is being held.
|
|
||||||
|
|
||||||
The `onKeyEvent()` method takes one argument: a reference to a `KeyEvent` object, which is a simple container for these essential bits of information:
|
|
||||||
|
|
||||||
- `event.addr` — the physical location of the keyswitch, if any
|
|
||||||
- `event.state` — a bitfield containing information on the current and previous state of the keyswitch (from which we can find out if it just toggled on or toggled off)
|
|
||||||
- `event.key` — a 16-bit `Key` value containing the contents looked up from the sketch's current keymap (if the key just toggled on) or the current live value of the key (if the key just toggled off)
|
|
||||||
|
|
||||||
Because the `KeyEvent` parameter is passed by (mutable) reference, our plugin's `onKeyEvent()` method can alter the components of the event, causing subsequent plugins (and, eventually, Kaleidoscope itself) to treat it as if it was a different event. In practice, except in very rare cases, the only member of a `KeyEvent` that a plugin should alter is `event.key`. Here's a very simple `onKeyEvent()` handler that changes all `X` keys into `Y` keys:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
|
||||||
if (event.key == Key_X)
|
|
||||||
event.key = Key_Y;
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### The difference between `ABORT` & `EVENT_CONSUMED`
|
|
||||||
|
|
||||||
Here's a plugin that will suppress all `X` key events:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
|
||||||
if (event.key == Key_X)
|
|
||||||
return EventHandlerResult::ABORT;
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Here's an almost identical plugin that has an odd failure mode:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
|
||||||
if (event.key == Key_X)
|
|
||||||
return EventHandlerResult::EVENT_CONSUMED;
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In this case, when an `X` key is pressed, no Keyboard HID report will be generated and sent to the host, but the key will still be recorded by Kaleidoscope as "live". If we hold that key down and press a `Y` key, we will suddenly see both `x` _and_ `y` in the output on the host. This is because returning `ABORT` suppresses the key event entirely, as if it never happened, whereas `EVENT_CONSUMED` signals to Kaleidoscope that the key should still become "live", but that no further processing is necessary. In this case, since we want to suppress all `X` keys entirely, we should return `ABORT`.
|
|
||||||
|
|
||||||
### A complete in-sketch plugin
|
|
||||||
|
|
||||||
Here's an example of a very simple plugin, defined as it would be in a firmware sketch (e.g. a `*.ino` file):
|
|
||||||
|
|
||||||
```c++
|
|
||||||
namespace kaleidoscope {
|
|
||||||
namespace plugin {
|
|
||||||
|
|
||||||
class KillX : public Plugin {
|
|
||||||
public:
|
|
||||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
|
||||||
if (event.key == Key_X)
|
|
||||||
return EventHandlerResult::ABORT;
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace kaleidoscope
|
|
||||||
} // namespace plugin
|
|
||||||
|
|
||||||
kaleidoscope::plugin::KillX;
|
|
||||||
```
|
|
||||||
|
|
||||||
On its own, this plugin won't have any effect unless we register it later in the sketch like this:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
KALEIDOSCOPE_INIT_PLUGINS(KillX);
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: `KALEIDOSCOPE_INIT_PLUGINS()` should only appear once in a sketch, with a list of all the plugins to be registered.
|
|
||||||
|
|
||||||
## Plugin registration order
|
|
||||||
|
|
||||||
Obviously, the `KillX` plugin isn't very useful. But more important, it's got a potential problem. Suppose we had another plugin defined, like so:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
namespace kaleidoscope {
|
|
||||||
namespace plugin {
|
|
||||||
|
|
||||||
class YtoX : public Plugin {
|
|
||||||
public:
|
|
||||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
|
||||||
if (event.key == Key_Y)
|
|
||||||
event.key = Key_X;
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace kaleidoscope
|
|
||||||
} // namespace plugin
|
|
||||||
|
|
||||||
kaleidoscope::plugin::YtoX;
|
|
||||||
```
|
|
||||||
|
|
||||||
`YtoX` changes any `Y` key to an `X` key. These two plugins both work fine on their own, but when we put them together, we get some undesirable behavior. Let's try it this way first:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
KALEIDOSCOPE_INIT_PLUGINS(YtoX, KillX);
|
|
||||||
```
|
|
||||||
|
|
||||||
This registers both plugins' event handlers with Kaleidoscope, in order, so for each `KeyEvent` generated in response to a keyswitch toggling on or off, `YtoX.onKeyEvent(event)` will get called first, then `KillX.onKeyEvent(event)` will get called.
|
|
||||||
|
|
||||||
If we press `X`, the `YtoX` plugin will effectively ignore the event, allowing it to pass through to `KillX`, which will abort the event.
|
|
||||||
|
|
||||||
If we press `Y`, `YtoX.onKeyEvent()` will change `event.key` from `Key_Y` to `Key_X`. Then, `KillX.onKeyEvent()` will abort the event. As a result, both `X` and `Y` keys will be suppressed by the combination of the two plugins.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Now, let's try the same two plugins in the other order:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
KALEIDOSCOPE_INIT_PLUGINS(KillX, YtoX);
|
|
||||||
```
|
|
||||||
|
|
||||||
If we press `X`, its keypress event will get aborted by `KillX.onKeyEvent()`, and that key will not become live, so when it gets released, the event generated won't have the value `Key_X`, but will instead by `Key_Inactive`, which will not result in anything happening, either from the plugins or from Kaleidoscope itself.
|
|
||||||
|
|
||||||
Things get interesting if we press and release `Y`, though. First, `KillX.onKeyEvent()` will simply return `OK`, allowing `YtoX.onKeyEvent()` to change `event.key` from `Key_Y` to `Key_X`, causing that `Key_X` to become live, and sending its keycode to the host in the Keyboard USB HID report. That's all as expected, but then we release the key, and that's were it goes wrong.
|
|
||||||
|
|
||||||
`KillX.onKeyEvent()` doesn't distinguish between presses and releases. When a key toggles off, rather than looking up that key's value in the keymap, Kaleidoscope takes it from the live keys array. That means that `event.key` will be `Key_X` when `KillX.onKeyEvent()` is called, which will result in that event being aborted. And when an event is aborted, the key's entry in the live keys array doesn't get updated, so Kaleidoscope will treat it as if the key is still held after release. Thus, far from preventing the keycode for `X` getting to the host, it keeps that key pressed forever! The `X` key becomes "stuck on" because the plugin suppresses both key _presses_ and key _releases_.
|
|
||||||
|
|
||||||
### Differentiating between press and release events
|
|
||||||
|
|
||||||
There is a solution to this problem, which is to have `KillX` suppress `Key_X` toggle-on events, but not toggle-off events:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
EventHandlerResult KillX::onKeyEvent(KeyEvent &event) {
|
|
||||||
if (event.key == Key_X && keyToggledOn(event.state))
|
|
||||||
return EventHandlerResult::ABORT;
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Kaleidoscope provides `keyToggledOn()` and `keyToggledOff()` functions that operate on the `event.state` bitfield, allowing plugins to differentiate between the two different event states. With this new version of the `KillX` plugin, it won't keep an `X` key live, but it will stop one from _becoming_ live.
|
|
||||||
|
|
||||||
Our two plugins still yield results that depend on registration order in `KALEIDOSCOPE_INIT_PLUGINS()`, but the bug where the `X` key becomes "stuck on" is gone.
|
|
||||||
|
|
||||||
It is very common for plugins to only act on key toggle-on events, or to respond differently to toggle-on and toggle-off events.
|
|
||||||
|
|
||||||
## Timers
|
|
||||||
|
|
||||||
Another thing that many plugins need to do is handle timeouts. For example, the OneShot plugin keeps certain keys live for a period of time after those keys are released. Kaleidoscope provides some infrastructure to help us keep track of time, starting with the `afterEachCycle()` "event" handler function.
|
|
||||||
|
|
||||||
The `onKeyEvent()` handlers only get called in response to keyswitches toggling on and off (or as a result of plugins calling `Runtime.handleKeyEvent()`). If the user isn't actively typing for a period, its `onKeyEvent()` handler won't get called at all, so it's not very useful to check timers in that function. Instead, if we need to know if a timer has expired, we need to do it in a function that gets called regularly, regardless of input. The `afterEachCycle()` handler gets called once per cycle, guaranteed.
|
|
||||||
|
|
||||||
This is what an `afterEachCycle()` handler looks like:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
EventHandlerResult afterEachCycle() {
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
It returns an `EventHandlerResult`, like other event handlers, but this one's return value is ignored by Kaleidoscope; returning `ABORT` or `EVENT_CONSUMED` has no effect on other plugins.
|
|
||||||
|
|
||||||
In addition to this, we need a way to keep track of time. For this, Kaleidoscope provides the function `Runtime.millisAtCycleStart()`, which returns an unsigned integer representing the number of milliseconds that have elapsed since the keyboard started. It's a 32-bit integer, so it won't overflow until about one month has elapsed, but we usually want to use as few bytes of RAM as possible on our MCU, so most timers store only as many bytes as needed, usually a `uint16_t`, which overflows after about one minute, or even a `uint8_t`, which is good for up to a quarter of a second.
|
|
||||||
|
|
||||||
We need to use an integer type that's at least as big as the longest timeout we expect to be used, but integer overflow can still give us the wrong answer if we check it by naïvely comparing the current time to the time at expiration, so Kaleidoscope provides a timeout-checking service that's handles the integer overflow properly: `Runtime.hasTimeExpired(start_time, timeout)`. To use it, your plugin should store a timestamp when the timer begins, using `Runtime.millisAtCycleStart()` (usually set in response to an event in `onKeyEvent()`). Then, in its `afterEachCycle()` call `hasTimeExpired()`:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
namespace kaleidoscope {
|
|
||||||
namespace plugin {
|
|
||||||
|
|
||||||
class MyPlugin : public Plugin {
|
|
||||||
public:
|
|
||||||
constexpr uint16_t timeout = 500;
|
|
||||||
|
|
||||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
|
||||||
if (event.key == Key_X && keyToggledOn(event.state)) {
|
|
||||||
start_time_ = Runtime.millisAtCycleStart();
|
|
||||||
timer_running_ = true;
|
|
||||||
}
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
EventHandlerResult afterEachCycle() {
|
|
||||||
if (Runtime.hasTimeExpired(start_time_, timeout)) {
|
|
||||||
timer_running_ = false;
|
|
||||||
// do something...
|
|
||||||
}
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool timer_running_ = false;
|
|
||||||
uint16_t start_time_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace kaleidoscope
|
|
||||||
} // namespace plugin
|
|
||||||
|
|
||||||
kaleidoscope::plugin::MyPlugin;
|
|
||||||
```
|
|
||||||
|
|
||||||
In the above example, the private member variable `start_time_` and the constant `timeout` are the same type of unsigned integer (`uint16_t`), and we've used the additional boolean `timer_running_` to keep from checking for timeouts when `start_time_` isn't valid. This plugin does something (unspecified) 500 milliseconds after a `Key_X` toggles on.
|
|
||||||
|
|
||||||
## Creating additional events
|
|
||||||
|
|
||||||
Another thing we might want a plugin to do is generate "extra" events that don't correspond to physical state changes. An example of this is the Macros plugin, which might turn a single keypress into a series of HID reports sent to the host. Let's build a simple plugin to illustrate how this is done, by making a key type a string of characters, rather than a single one.
|
|
||||||
|
|
||||||
For the sake of simplicity, let's make the key `H` result in the string `Hi!` being typed (from the point of view of the host computer). To do this, we'll make a plugin with an `onKeyEvent()` handler (because we want it to respond to a particular keypress event), which will call `Runtime.handleKeyEvent()` to generate new events sent to the host.
|
|
||||||
|
|
||||||
The first thing we need to understand to do this is how to use the `KeyEvent()` constructor to create a new `KeyEvent` object. For example:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
KeyEvent event = KeyEvent(KeyAddr::none(), IS_PRESSED, Key_H);
|
|
||||||
```
|
|
||||||
|
|
||||||
This creates a `KeyEvent` where `event.addr` is an invalid address that doesn't correspond to a physical keyswitch, `event.state` has only the `IS_PRESSED` bit set, but not `WAS_PRESSED`, which corresponds to a key toggle-on event, and `event.key` is set to `Key_H`.
|
|
||||||
|
|
||||||
We can then cause Kaleidoscope to process this event, including calling plugin handlers, by calling `Runtime.handleKeyEvent(event)`:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
|
||||||
if (event.key == Key_H && keyToggledOn(event.state)) {
|
|
||||||
|
|
||||||
// Create and send the `H` (shift + h)
|
|
||||||
KeyEvent new_event = KeyEvent(KeyAddr::none(), IS_PRESSED, LSHIFT(Key_H));
|
|
||||||
Runtime.handleKeyEvent(new_event);
|
|
||||||
|
|
||||||
// Change the key value and send `i`
|
|
||||||
new_event.key = Key_I;
|
|
||||||
Runtime.handleKeyEvent(new_event);
|
|
||||||
|
|
||||||
// Change the key value and send `!` (shift + 1)
|
|
||||||
new_event.key = LSHIFT(Key_1);
|
|
||||||
Runtime.handleKeyEvent(new_event);
|
|
||||||
|
|
||||||
return EventHandlerResult::ABORT;
|
|
||||||
}
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
A few shortcuts were taken with this plugin that are worth pointing out. First, you may have noticed that we didn't send any key _release_ events, just three presses. This works, but there's a small chance that it could cause problems for some plugin that's trying to match key presses and releases. To be nice (or pedantic, if you will), we could also send the matching release events, but this is probably not necessary in this case, because we've used an invalid key address (`KeyAddr::none()`) for these generated events. This means that Kaleidoscope will not be recording these events as held keys. If we had used valid key addresses (corresponding to physical keyswitches) instead, it would be more important to send matching release events to keep keys from getting "stuck" on. For example, we could just use the address of the `H` key that was pressed:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
|
||||||
if (event.key == Key_H && keyToggledOn(event.state)) {
|
|
||||||
|
|
||||||
KeyEvent new_event = KeyEvent(event.addr, IS_PRESSED, LSHIFT(Key_H));
|
|
||||||
Runtime.handleKeyEvent(new_event);
|
|
||||||
|
|
||||||
new_event.key = Key_I;
|
|
||||||
Runtime.handleKeyEvent(new_event);
|
|
||||||
|
|
||||||
new_event.key = LSHIFT(Key_1);
|
|
||||||
Runtime.handleKeyEvent(new_event);
|
|
||||||
|
|
||||||
return EventHandlerResult::ABORT;
|
|
||||||
}
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This new version has the curious property that if the `H` key is held long enough, it will result in repeating `!!!!` characters on the host, until the key is released, which will clear it. In fact, instead of creating a whole new `KeyEvent` object, we could further simplify this plugin by simply modifying the `event` object that we already have, instead:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
|
||||||
if (event.key == Key_H && keyToggledOn(event.state)) {
|
|
||||||
event.key = LSHIFT(Key_H);
|
|
||||||
Runtime.handleKeyEvent(event);
|
|
||||||
|
|
||||||
event.key = Key_I;
|
|
||||||
Runtime.handleKeyEvent(event);
|
|
||||||
|
|
||||||
event.key = LSHIFT(Key_1);
|
|
||||||
}
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that, with this version, we've only sent two extra events, then changed the `event.key` value, and returned `OK` instead of `ABORT`. This is basically the same as the above pluging that turned `Y` into `X`, but with two extra events sent first.
|
|
||||||
|
|
||||||
As one extra precaution, it would be wise to mark the generated event(s) as "injected" to let other plugins know that these events should be ignored. This is a convention that is used by many existing Kaleidoscope plugins. We do this by setting the `INJECTED` bit in the `event.state` variable:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
|
||||||
if (event.key == Key_H && keyToggledOn(event.state)) {
|
|
||||||
event.state |= INJECTED;
|
|
||||||
|
|
||||||
event.key = LSHIFT(Key_H);
|
|
||||||
Runtime.handleKeyEvent(event);
|
|
||||||
|
|
||||||
event.key = Key_I;
|
|
||||||
Runtime.handleKeyEvent(event);
|
|
||||||
|
|
||||||
event.key = LSHIFT(Key_1);
|
|
||||||
}
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If we wanted to be especially careful, we could also add the corresponding release events:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
|
||||||
if (event.key == Key_H && keyToggledOn(event.state)) {
|
|
||||||
event.key = LSHIFT(Key_H);
|
|
||||||
event.state = INJECTED | IS_PRESSED;
|
|
||||||
Runtime.handleKeyEvent(event);
|
|
||||||
event.state = INJECTED | WAS_PRESSED;
|
|
||||||
Runtime.handleKeyEvent(event);
|
|
||||||
|
|
||||||
event.key = Key_I;
|
|
||||||
event.state = INJECTED | IS_PRESSED;
|
|
||||||
Runtime.handleKeyEvent(event);
|
|
||||||
event.state = INJECTED | WAS_PRESSED;
|
|
||||||
Runtime.handleKeyEvent(event);
|
|
||||||
|
|
||||||
event.key = LSHIFT(Key_1);
|
|
||||||
event.state = INJECTED | IS_PRESSED;
|
|
||||||
}
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Avoiding infinite loops
|
|
||||||
|
|
||||||
One very important consideration for any plugin that calls `Runtime.handleKeyEvent()` from an `onKeyEvent()` handler is recursion. `Runtime.handleKeyEvent()` will call all plugins' `onKeyEvent()` handlers, including the one that generated the event. Therefore, we need to take some measures to short-circuit the resulting recursive call so that our plugin doesn't cause an infinite loop.
|
|
||||||
|
|
||||||
Suppose the example plugin above was changed to type the string `hi!` instead of `Hi!`. When sending the first generated event, with `event.key` set to `Key_H`, our plugin would recognize that event as one that should be acted on, and make another call to `Runtime.handleKeyEvent()`, which would again call `MyPlugin.onKeyEvent()`, and so on until the MCU ran out of memory on the stack.
|
|
||||||
|
|
||||||
The simplest mechanism used by many plugins that mark their generated events "injected" is to simply ignore all generated events:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
|
||||||
if ((event.state & INJECTED) != 0)
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
|
|
||||||
if (event.key == Key_H && keyToggledOn(event.state)) {
|
|
||||||
event.state |= INJECTED;
|
|
||||||
|
|
||||||
event.key = LSHIFT(Key_H);
|
|
||||||
Runtime.handleKeyEvent(event);
|
|
||||||
|
|
||||||
event.key = Key_I;
|
|
||||||
Runtime.handleKeyEvent(event);
|
|
||||||
|
|
||||||
event.key = LSHIFT(Key_1);
|
|
||||||
}
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
There are other techniques to avoid inifinite loops, employed by plugins whose injected events should be processed by other plugins, but since most of those will be using the `onKeyswitchEvent()` handler instead of `onKeyEvent()`, we'll cover that later in this guide.
|
|
||||||
|
|
||||||
## Physical keyswitch events
|
|
||||||
|
|
||||||
Most plugins that respond to key events can do their work using the `onKeyEvent()` handler, but in some cases, it's necessary to use the `onKeyswitchEvent()` handler instead. These event handlers are strictly intended for physical keyswitch events, and plugins that implement the `onKeyswitchEvent()` handler must abide by certain rules in order to work well with each other. As a result, such a plugin is a bit more complex, but there are helper mechanisms to make things easier:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
#include "kaleidoscope/KeyEventTracker.h"
|
|
||||||
|
|
||||||
namespace kaleidoscope {
|
|
||||||
namespace plugin {
|
|
||||||
|
|
||||||
class MyKeyswitchPlugin : public Plugin {
|
|
||||||
public:
|
|
||||||
EventHandlerResult onKeyswitchEvent(KeyEvent &event) {
|
|
||||||
if (event_tracker_.shouldIgnore(event))
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
// Plugin logic goes here...
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
KeyEventTracker event_tracker_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace kaleidoscope
|
|
||||||
} // namespace plugin
|
|
||||||
|
|
||||||
kaleidoscope::plugin::MyKeyswitchPlugin;
|
|
||||||
```
|
|
||||||
|
|
||||||
We've just added a `KeyEventTracker` object to our plugin, and made the first line of its `onKeyswitchEvent()` handler call that event tracker's `shouldIgnore()` method, returning `OK` if it returns `true` (thereby ignoring the event). Every plugin that implements `onKeyswitchEvent()` should follow this template to avoid plugin interaction bugs, including possible infinite loops.
|
|
||||||
|
|
||||||
The main reason for this event tracker mechanism is that plugins with `onKeyswitchEvent()` handlers often delay events because some aspect of those events (usually `event.key`) needs to be determined by subsequent events or timeouts. To do this, event information is stored, and the event is later regenerated by the plugin, which calls `Runtime.handleKeyswitchEvent()` so that the other `onKeyswitchEvent()` handlers can process it.
|
|
||||||
|
|
||||||
We need to prevent infinite loops, but simply marking the regenerated event `INJECTED` is no good, because it would prevent the other plugins from acting on it, so we instead keep track of a monotonically increasing event id number and use the `KeyEventTracker` helper class to ignore events that our plugin has already recieved, so that when the plugin regenerates an event with the same event id, it (and all the plugins before it) can ignore that event, but the subsequent plugins, which haven't seen that event yet, will recongize it as new and process the event accordingly.
|
|
||||||
|
|
||||||
### Regenerating stored events
|
|
||||||
|
|
||||||
When a plugin that implements `onKeyswitchEvent()` regenerates a stored event later so that it can be processed by the next plugin in the chain, it must use the correct event id value (the same one used by the original event). This is an object of type `EventId`, and is retrieved by calling `event.id()` (unlike the other properties of a `KeyEvent` object the event id is not directly accessible).
|
|
||||||
|
|
||||||
```c++
|
|
||||||
KeyEventId stored_id = event.id();
|
|
||||||
```
|
|
||||||
|
|
||||||
When reconstructing an event to allow it to proceed, we then use the four-argument version of the `KeyEvent` constructor:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
KeyEvent event = KeyEvent(addr, state, key, stored_id);
|
|
||||||
```
|
|
||||||
|
|
||||||
In the above, `addr` and `state` are usually also the same as the original event's values, and `key` is most often the thing that changes. If your plugin wants a keymap lookup to take place, the value `Key_Undefined` can be used instead of explicitly doing the lookup itself.
|
|
||||||
|
|
||||||
## Controlling LEDs
|
|
||||||
|
|
||||||
## HID reports
|
|
||||||
|
|
||||||
## Layer changes
|
|
@ -1,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,35 @@
|
|||||||
|
# TriColor
|
||||||
|
|
||||||
|
The `TriColor` effect extension is a part of
|
||||||
|
the [`LEDEffects`][plugin:ledeffects] library, not a stand-alone base library of
|
||||||
|
its own. It is used to implement the effects in that library.
|
||||||
|
|
||||||
|
[plugin:ledeffects]: LEDEffects.md
|
||||||
|
|
||||||
|
It is a class that can be used to create LED effects that all follow a similar
|
||||||
|
pattern: alphas and similar in one color; modifiers, special keys, and half the
|
||||||
|
function keys in another, and `Esc` in a third (this latter being optional). If
|
||||||
|
we have a color scheme that follows this pattern, the `TriColor` extension can
|
||||||
|
make it a lot easier to implement it.
|
||||||
|
|
||||||
|
## Using the extension
|
||||||
|
|
||||||
|
Because the extension is part of the [`LEDEffects`][plugin:ledeffects] library,
|
||||||
|
we need to include that header:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
#include <Kaleidoscope-LEDEffects.h>
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, we simply create a new instance of the `TriColor` class, with appropriate
|
||||||
|
colors set for the constructor:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
kaleidoscope::plugin::TriColor BlackAndWhiteEffect (CRGB(0x00, 0x00, 0x00),
|
||||||
|
CRGB(0xff, 0xff, 0xff),
|
||||||
|
CRGB(0x80, 0x80, 0x80));
|
||||||
|
```
|
||||||
|
|
||||||
|
The first argument is the base color, the second is for modifiers and special
|
||||||
|
keys, the last one is for the `Esc` key. If the last one is omitted, the
|
||||||
|
extension will use the modifier color for it.
|
@ -0,0 +1,274 @@
|
|||||||
|
# -*- shell-script -*-
|
||||||
|
|
||||||
|
## NEEDS: SKETCH
|
||||||
|
## Should be included when the current directory is the dir of the Sketch.
|
||||||
|
|
||||||
|
SKETCH="${SKETCH:-${DEFAULT_SKETCH}}"
|
||||||
|
|
||||||
|
########
|
||||||
|
######## Keyboard hardware definitions
|
||||||
|
########
|
||||||
|
|
||||||
|
: "${BOARD:=model01}"
|
||||||
|
: "${MCU:=atmega32u4}"
|
||||||
|
|
||||||
|
if [ -z "${ARCH}" ]; then
|
||||||
|
ARCH=$(echo "${FQBN}" | sed -n -e 's/^[^:]\+:\([^:]\+\).*/\1/p')
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${ARCH}" = "virtual" ]; then
|
||||||
|
: "${FQBN:=keyboardio:virtual:${BOARD}}"
|
||||||
|
|
||||||
|
# Set the compiler path for virtual builds
|
||||||
|
#
|
||||||
|
if [ -z "${COMPILER_PATH}" ]; then
|
||||||
|
COMPILER_PATH="/usr/bin/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
COMPILER_PREFIX=""
|
||||||
|
else
|
||||||
|
ARCH="avr"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${FQBN}" ]; then
|
||||||
|
: "${FQBN:=keyboardio:avr:${BOARD}}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
########
|
||||||
|
######## Host OS specific commands
|
||||||
|
########
|
||||||
|
|
||||||
|
## Platform-specific overrides
|
||||||
|
# Shamelessly stolen from git's Makefile
|
||||||
|
uname_S=$(uname -s 2>/dev/null || echo not)
|
||||||
|
uname_O=$(uname -o 2>/dev/null || echo not)
|
||||||
|
|
||||||
|
|
||||||
|
find_device_vid_pid() {
|
||||||
|
: ${VID:=$(get_arduino_pref 'build.vid')}
|
||||||
|
: ${SKETCH_PID:=$(get_arduino_pref 'build.pid')}
|
||||||
|
: ${BOOTLOADER_PID:=$(get_arduino_pref 'bootloader.pid')}
|
||||||
|
: ${BOOTLOADER_VID:=$(get_arduino_pref 'bootloader.vid')}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get_arduino_pref() {
|
||||||
|
pref=$1
|
||||||
|
# Strip the preference name. And then strip leading and trailing quotations
|
||||||
|
MESSAGE=$(dump_arduino_prefs | grep --max-count=1 ${pref}= | sed -e s/^.*${pref}=// -e 's/^"//' -e 's/"$//')
|
||||||
|
echo $MESSAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dump_arduino_prefs() {
|
||||||
|
# SKETCH and -build-path in this command are here because of a bug introduced in Arduino 1.8.10
|
||||||
|
# https://github.com/arduino/arduino-builder/issues/341
|
||||||
|
|
||||||
|
if [ "x${_ARDUINO_PREFS}x" == "xx" ]; then
|
||||||
|
|
||||||
|
_ARDUINO_PREFS=$("${ARDUINO_BUILDER}" \
|
||||||
|
-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}" \
|
||||||
|
-build-path "${ARDUINO_PATH}" \
|
||||||
|
-dump-prefs "${SKETCH_DIR}/${SKETCH}.ino" )
|
||||||
|
fi
|
||||||
|
echo "$_ARDUINO_PREFS"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
find_device_port() {
|
||||||
|
find_device_vid_pid
|
||||||
|
DEVICE_PORT_PROBER="${KALEIDOSCOPE_BIN_DIR}/find-device-port-linux-udev"
|
||||||
|
if [[ "${DEVICE_PORT}" = "" ]]; then
|
||||||
|
DEVICE_PORT="$(perl ${DEVICE_PORT_PROBER} ${VID} ${SKETCH_PID})"
|
||||||
|
else
|
||||||
|
echo "DEVICE_PORT=\"${DEVICE_PORT}\" predefined."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_device_cmd() {
|
||||||
|
if [ -z ${NO_RESET} ]; then
|
||||||
|
stty -F ${DEVICE_PORT} 1200 hupcl
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
find_bootloader_ports() {
|
||||||
|
find_device_vid_pid
|
||||||
|
: "${BOOTLOADER_VID:=${VID}}"
|
||||||
|
DEVICE_PORT_PROBER="${KALEIDOSCOPE_BIN_DIR}/find-device-port-linux-udev"
|
||||||
|
if [[ "${DEVICE_PORT_BOOTLOADER}" = "" ]]; then
|
||||||
|
DEVICE_PORT_BOOTLOADER="$(perl ${DEVICE_PORT_PROBER} ${BOOTLOADER_VID} ${BOOTLOADER_PID})"
|
||||||
|
else
|
||||||
|
echo "DEVICE_PORT_BOOTLOADER=\"${DEVICE_PORT_BOOTLOADER}\" predefined."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MD5="md5sum"
|
||||||
|
|
||||||
|
if [ "${uname_S}" = "Darwin" ]; then
|
||||||
|
|
||||||
|
find_device_port() {
|
||||||
|
DEVICE_PORT_PROBER="${KALEIDOSCOPE_BIN_DIR}/find-device-port-macos"
|
||||||
|
DEVICE_PORT="$(perl ${DEVICE_PORT_PROBER} ${VID} ${SKETCH_PID})"
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_device_cmd() {
|
||||||
|
/bin/stty -f ${DEVICE_PORT} 1200
|
||||||
|
}
|
||||||
|
|
||||||
|
: "${ARDUINO_PATH:=/Applications/Arduino.app/Contents/Java/}"
|
||||||
|
: "${ARDUINO_PACKAGE_PATH:=${HOME}/Library/Arduino15/packages}"
|
||||||
|
: "${ARDUINO_LOCAL_LIB_PATH:=${HOME}/Documents/Arduino}"
|
||||||
|
|
||||||
|
MD5="md5"
|
||||||
|
|
||||||
|
find_bootloader_ports() {
|
||||||
|
find_device_vid_pid
|
||||||
|
: "${BOOTLOADER_VID:=${VID}}"
|
||||||
|
DEVICE_PORT_PROBER="${KALEIDOSCOPE_BIN_DIR}/find-device-port-macos"
|
||||||
|
if [[ "${DEVICE_PORT_BOOTLOADER}" = "" ]]; then
|
||||||
|
DEVICE_PORT_BOOTLOADER="$(perl ${DEVICE_PORT_PROBER} ${BOOTLOADER_VID} ${BOOTLOADER_PID})"
|
||||||
|
else
|
||||||
|
echo "DEVICE_PORT_BOOTLOADER=\"${DEVICE_PORT_BOOTLOADER}\" predefined."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
elif [ "${uname_S}" = "FreeBSD" ]; then
|
||||||
|
|
||||||
|
find_device_port() {
|
||||||
|
DEVICE_PORT_PROBER="${KALEIDOSCOPE_BIN_DIR}/find-device-port-freebsd"
|
||||||
|
DEVICE_PORT="$(perl ${DEVICE_PORT_PROBER})"
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_device_cmd() {
|
||||||
|
/bin/stty -f ${DEVICE_PORT} 1200
|
||||||
|
}
|
||||||
|
|
||||||
|
MD5="md5"
|
||||||
|
: "${AVR_SIZE:=/usr/local/bin/avr-size}"
|
||||||
|
: "${AVR_NM:=/usr/local/bin/avr-nm}"
|
||||||
|
: "${AVR_OBJDUMP:=/usr/local/bin/avr-objdump}"
|
||||||
|
: "${AVRDUDE:=/usr/local/bin/avrdude}"
|
||||||
|
: "${AVRDUDE_CONF:=/usr/local/etc/avrdude.conf}"
|
||||||
|
: "${ARDUINO_BUILDER:=/usr/local/bin/arduino-builder}"
|
||||||
|
|
||||||
|
find_bootloader_ports() {
|
||||||
|
DEVICE_PORT_PROBER="${KALEIDOSCPE_BIN_DIR}/find-device-port-freebsd"
|
||||||
|
DEVICE_PORT_BOOTLOADER="$(perl ${DEVICE_PORT_PROBER})"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "${ARCH}" = "virtual" ]; then
|
||||||
|
: "${COMPILER_PATH:=/usr/local/bin/}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif [ "${uname_O}" = "Cygwin" ]; then
|
||||||
|
# The Windows arduino-builder.exe doesn't understand being told to exec against Cygwin symlinks
|
||||||
|
CCACHE_NOT_SUPPORTED=1
|
||||||
|
|
||||||
|
# Note: the default ARDUINO_PATH here is the default Arduino installation path on Windows, but it won't actually
|
||||||
|
# work in practice right now since we haven't fixed all bugs related to interpretation of spaces in these paths.
|
||||||
|
#
|
||||||
|
# It's important that all of these be underneath /cygdrive/c so they can be converted to Windows paths that the
|
||||||
|
# Windows Arduino binaries can understand.
|
||||||
|
: "${ARDUINO_PATH:=/cygdrive/c/Program\ Files\ (x86)/Arduino}"
|
||||||
|
: "${ARDUINO_PACKAGE_PATH:=/cygdrive/c/Users/${USER}/AppData/Local/Arduino15/packages}"
|
||||||
|
: "${ARDUINO_LOCAL_LIB_PATH:=/cygdrive/c/Users/${USER}/Arduino}"
|
||||||
|
TMPDIR="${ARDUINO_LOCAL_LIB_PATH:-/cygdrive/c/Users/${USER}/AppData/Local/Temp}"
|
||||||
|
|
||||||
|
# We need to prevent Windows executables from being passed parameters that are absolute paths, since they won't
|
||||||
|
# be interpretable when of the form /cygdrive/c/foo. To work around this, we set the common path root variables
|
||||||
|
# to use relative paths instead of absolute paths, since those have mostly platform-agnostic behavior.
|
||||||
|
#
|
||||||
|
# Note that this trick requires that all of these paths exist on the same drive letter as the current directory,
|
||||||
|
# since otherwise even the relative paths would include Cygwin-specific components. So...
|
||||||
|
if [[ $(realpath --relative-base=/cygdrive/c .) == /* ]]; then
|
||||||
|
echo "kaleidoscope-builder's Cygwin support is currently limited to running from within /cygdrive/c"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ARDUINO_PATH="$(realpath --relative-to=./ ${ARDUINO_PATH})"
|
||||||
|
ARDUINO_PACKAGE_PATH="$(realpath --relative-to=./ ${ARDUINO_PACKAGE_PATH})"
|
||||||
|
ARDUINO_LOCAL_LIB_PATH="$(realpath --relative-to=./ ${ARDUINO_LOCAL_LIB_PATH})"
|
||||||
|
TMPDIR="$(realpath --relative-to=./ ${ARDUINO_PATH})"
|
||||||
|
|
||||||
|
find_device_port() {
|
||||||
|
find_device_vid_pid
|
||||||
|
DEVICE_PORT_PROBER="${KALEIDOSCOPE_BIN_DIR}/find-device-port-windows.ps1"
|
||||||
|
DEVICE_PORT="$(powershell -noprofile -executionpolicy bypass ${DEVICE_PORT_PROBER} ${VID} ${SKETCH_PID} -Format Cygwin)"
|
||||||
|
DEVICE_COM_PORT="$(powershell -noprofile -executionpolicy bypass ${DEVICE_PORT_PROBER} ${VID} ${SKETCH_PID} -Format COM)"
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_device_cmd() {
|
||||||
|
cmd /c mode ${DEVICE_COM_PORT} baud=1200
|
||||||
|
}
|
||||||
|
|
||||||
|
find_bootloader_ports() {
|
||||||
|
find_device_vid_pid
|
||||||
|
: "${BOOTLOADER_VID:=${VID}}"
|
||||||
|
DEVICE_PORT_PROBER="${KALEIDOSCOPE_BIN_DIR}/find-device-port-windows.ps1"
|
||||||
|
DEVICE_PORT_BOOTLOADER="$(powershell -noprofile -executionpolicy bypass ${DEVICE_PORT_PROBER} ${BOOTLOADER_VID} ${BOOTLOADER_PID} -Format COM)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
######
|
||||||
|
###### Arduino tools configuration
|
||||||
|
######
|
||||||
|
|
||||||
|
: "${ARDUINO_PATH:=/usr/local/arduino}"
|
||||||
|
: "${ARDUINO_LOCAL_LIB_PATH:=${HOME}/Arduino}"
|
||||||
|
: "${ARDUINO_TOOLS_PATH:=${ARDUINO_PATH}/hardware/tools}"
|
||||||
|
: "${ARDUINO_PACKAGE_PATH:=${HOME}/.arduino15/packages}"
|
||||||
|
: "${ARDUINO_BUILDER:=${ARDUINO_PATH}/arduino-builder}"
|
||||||
|
: "${ARDUINO_BUILDER_TOOLS_PATH:=${ARDUINO_PATH}/tools-builder}"
|
||||||
|
|
||||||
|
ARDUINO_IDE_VERSION="10607"
|
||||||
|
|
||||||
|
######
|
||||||
|
###### Executable paths
|
||||||
|
######
|
||||||
|
|
||||||
|
# Allow the compiler path to be empty for virtual builds
|
||||||
|
: "${COMPILER_PATH=${ARDUINO_TOOLS_PATH}/avr/bin/}"
|
||||||
|
|
||||||
|
COMPILER_SUFFIX=""
|
||||||
|
|
||||||
|
C_COMPILER_BASENAME=$(basename ${CC:-gcc})
|
||||||
|
CXX_COMPILER_BASENAME=$(basename ${CXX:-g++})
|
||||||
|
AR_BASENAME=$(basename ${AR:-ar})
|
||||||
|
OBJCOPY_BASENAME=$(basename ${OBJCOPY:-objcopy})
|
||||||
|
|
||||||
|
# Allow the compiler prefix to be empty for virtual builds
|
||||||
|
COMPILER_PREFIX="${COMPILER_PREFIX-avr-}"
|
||||||
|
: "${AVR_SIZE:=${COMPILER_PATH}/${COMPILER_PREFIX}size}"
|
||||||
|
: "${AVR_SIZE_FLAGS:=-C --mcu=${MCU}}"
|
||||||
|
: "${AVR_OBJDUMP:=${COMPILER_PATH}/${COMPILER_PREFIX}objdump}"
|
||||||
|
: "${AVR_OBJCOPY:=${COMPILER_PATH}/${COMPILER_PREFIX}objcopy}"
|
||||||
|
: "${AVR_NM:=${COMPILER_PATH}/${COMPILER_PREFIX}nm}"
|
||||||
|
: "${AVR_AR:=${COMPILER_PATH}/${COMPILER_PREFIX}ar}"
|
||||||
|
: "${AVR_GCC:=${COMPILER_PATH}/${COMPILER_PREFIX}${C_COMPILER_BASENAME}}"
|
||||||
|
AVR_GPLUSPLUS="${AVR_GCC:-${COMPILER_PATH}/${COMPILER_PREFIX}${CXX_COMPILER_BASENAME}}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
: "${AVRDUDE:=${ARDUINO_TOOLS_PATH}/avr/bin/avrdude}"
|
||||||
|
: "${AVRDUDE_CONF:=${ARDUINO_TOOLS_PATH}/avr/etc/avrdude.conf}"
|
||||||
|
|
||||||
|
######
|
||||||
|
###### Source files and dependencies
|
||||||
|
######
|
||||||
|
|
||||||
|
: "${BOARD_HARDWARE_PATH:=${ARDUINO_LOCAL_LIB_PATH}/hardware}"
|
||||||
|
|
||||||
|
if [ ! -z "${ARDUINO_TOOLS_PATH}" ]; then
|
||||||
|
ARDUINO_TOOLS_PARAM="${ARDUINO_TOOLS_PATH}"
|
||||||
|
ARDUINO_TOOLS_FLAG="-tools"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "${AVR_GCC_PREFIX}" ]; then
|
||||||
|
ARDUINO_AVR_GCC_PREFIX_PARAM="-prefs \"runtime.tools.avr-gcc.path=${AVR_GCC_PREFIX}\""
|
||||||
|
fi
|
@ -0,0 +1,2 @@
|
|||||||
|
DEFAULT_SKETCH="Atreus"
|
||||||
|
BOARD="keyboardio_atreus"
|
@ -1,618 +0,0 @@
|
|||||||
// -*- mode: c++ -*-
|
|
||||||
// Copyright 2016-2022 Keyboardio, inc. <jesse@keyboard.io>
|
|
||||||
// See "LICENSE" for license details
|
|
||||||
|
|
||||||
/**
|
|
||||||
* These #include directives pull in the Kaleidoscope firmware core,
|
|
||||||
* as well as the Kaleidoscope plugins we use in the Model 100's firmware
|
|
||||||
*/
|
|
||||||
|
|
||||||
// The Kaleidoscope core
|
|
||||||
#include "Kaleidoscope.h"
|
|
||||||
|
|
||||||
// Support for storing the keymap in EEPROM
|
|
||||||
#include "Kaleidoscope-EEPROM-Settings.h"
|
|
||||||
#include "Kaleidoscope-EEPROM-Keymap.h"
|
|
||||||
|
|
||||||
// Support for communicating with the host via a simple Serial protocol
|
|
||||||
#include "Kaleidoscope-FocusSerial.h"
|
|
||||||
|
|
||||||
// Support for querying the firmware version via Focus
|
|
||||||
#include "Kaleidoscope-FirmwareVersion.h"
|
|
||||||
|
|
||||||
// Support for keys that move the mouse
|
|
||||||
// #include "Kaleidoscope-MouseKeys.h"
|
|
||||||
|
|
||||||
// Support for macros
|
|
||||||
#include "Kaleidoscope-Macros.h"
|
|
||||||
|
|
||||||
// Support for controlling the keyboard's LEDs
|
|
||||||
#include "Kaleidoscope-LEDControl.h"
|
|
||||||
|
|
||||||
// Support for "Numpad" mode, which is mostly just the Numpad specific LED mode
|
|
||||||
// #include "Kaleidoscope-NumPad.h"
|
|
||||||
|
|
||||||
// Support for the "Boot greeting" effect, which pulses the 'LED' button for 10s
|
|
||||||
// when the keyboard is connected to a computer (or that computer is powered on)
|
|
||||||
#include "Kaleidoscope-LEDEffect-BootGreeting.h"
|
|
||||||
|
|
||||||
// Support for LED modes that set all LEDs to a single color
|
|
||||||
#include "Kaleidoscope-LEDEffect-SolidColor.h"
|
|
||||||
|
|
||||||
// Support for an LED mode that makes all the LEDs 'breathe'
|
|
||||||
#include "Kaleidoscope-LEDEffect-Breathe.h"
|
|
||||||
|
|
||||||
// Support for an LED mode that makes a red pixel chase a blue pixel across the keyboard
|
|
||||||
#include "Kaleidoscope-LEDEffect-Chase.h"
|
|
||||||
|
|
||||||
// Support for LED modes that pulse the keyboard's LED in a rainbow pattern
|
|
||||||
#include "Kaleidoscope-LEDEffect-Rainbow.h"
|
|
||||||
|
|
||||||
// Support for an LED mode that lights up the keys as you press them
|
|
||||||
#include "Kaleidoscope-LED-Stalker.h"
|
|
||||||
|
|
||||||
// Support for an LED mode that prints the keys you press in letters 4px high
|
|
||||||
#include "Kaleidoscope-LED-AlphaSquare.h"
|
|
||||||
|
|
||||||
// Support for shared palettes for other plugins, like Colormap below
|
|
||||||
#include "Kaleidoscope-LED-Palette-Theme.h"
|
|
||||||
|
|
||||||
// Support for an LED mode that lets one configure per-layer color maps
|
|
||||||
#include "Kaleidoscope-Colormap.h"
|
|
||||||
|
|
||||||
// Support for turning the LEDs off after a certain amount of time
|
|
||||||
#include "Kaleidoscope-IdleLEDs.h"
|
|
||||||
|
|
||||||
// Support for setting and saving the default LED mode
|
|
||||||
#include "Kaleidoscope-DefaultLEDModeConfig.h"
|
|
||||||
|
|
||||||
// Support for Keyboardio's internal keyboard testing mode
|
|
||||||
#include "Kaleidoscope-HardwareTestMode.h"
|
|
||||||
|
|
||||||
// Support for host power management (suspend & wakeup)
|
|
||||||
#include "Kaleidoscope-HostPowerManagement.h"
|
|
||||||
|
|
||||||
// Support for magic combos (key chords that trigger an action)
|
|
||||||
#include "Kaleidoscope-MagicCombo.h"
|
|
||||||
|
|
||||||
// Support for USB quirks, like changing the key state report protocol
|
|
||||||
#include "Kaleidoscope-USB-Quirks.h"
|
|
||||||
|
|
||||||
// Support for secondary actions on keys
|
|
||||||
#include "Kaleidoscope-Qukeys.h"
|
|
||||||
|
|
||||||
// Support for one-shot modifiers and layer keys
|
|
||||||
#include "Kaleidoscope-OneShot.h"
|
|
||||||
#include "Kaleidoscope-Escape-OneShot.h"
|
|
||||||
|
|
||||||
// Support for dynamic, Chrysalis-editable macros
|
|
||||||
#include "Kaleidoscope-DynamicMacros.h"
|
|
||||||
|
|
||||||
// Support for SpaceCadet keys
|
|
||||||
#include "Kaleidoscope-SpaceCadet.h"
|
|
||||||
|
|
||||||
// Support for editable layer names
|
|
||||||
#include "Kaleidoscope-LayerNames.h"
|
|
||||||
|
|
||||||
// Support for the GeminiPR Stenography protocol
|
|
||||||
// #include "Kaleidoscope-Steno.h"
|
|
||||||
|
|
||||||
/** This 'enum' is a list of all the macros used by the Model 100's firmware
|
|
||||||
* The names aren't particularly important. What is important is that each
|
|
||||||
* is unique.
|
|
||||||
*
|
|
||||||
* These are the names of your macros. They'll be used in two places.
|
|
||||||
* The first is in your keymap definitions. There, you'll use the syntax
|
|
||||||
* `M(MACRO_NAME)` to mark a specific keymap position as triggering `MACRO_NAME`
|
|
||||||
*
|
|
||||||
* The second usage is in the 'switch' statement in the `macroAction` function.
|
|
||||||
* That switch statement actually runs the code associated with a macro when
|
|
||||||
* a macro key is pressed.
|
|
||||||
*/
|
|
||||||
|
|
||||||
enum {
|
|
||||||
MACRO_VERSION_INFO,
|
|
||||||
MACRO_ANY,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/** The Model 100's key layouts are defined as 'keymaps'. By default, there are three
|
|
||||||
* keymaps: The standard QWERTY keymap, the "Function layer" keymap and the "Numpad"
|
|
||||||
* keymap.
|
|
||||||
*
|
|
||||||
* Each keymap is defined as a list using the 'KEYMAP_STACKED' macro, built
|
|
||||||
* of first the left hand's layout, followed by the right hand's layout.
|
|
||||||
*
|
|
||||||
* Keymaps typically consist mostly of `Key_` definitions. There are many, many keys
|
|
||||||
* defined as part of the USB HID Keyboard specification. You can find the names
|
|
||||||
* (if not yet the explanations) for all the standard `Key_` defintions offered by
|
|
||||||
* Kaleidoscope in these files:
|
|
||||||
* https://github.com/keyboardio/Kaleidoscope/blob/master/src/kaleidoscope/key_defs/keyboard.h
|
|
||||||
* https://github.com/keyboardio/Kaleidoscope/blob/master/src/kaleidoscope/key_defs/consumerctl.h
|
|
||||||
* https://github.com/keyboardio/Kaleidoscope/blob/master/src/kaleidoscope/key_defs/sysctl.h
|
|
||||||
* https://github.com/keyboardio/Kaleidoscope/blob/master/src/kaleidoscope/key_defs/keymaps.h
|
|
||||||
*
|
|
||||||
* Additional things that should be documented here include
|
|
||||||
* using ___ to let keypresses fall through to the previously active layer
|
|
||||||
* using XXX to mark a keyswitch as 'blocked' on this layer
|
|
||||||
* using ShiftToLayer() and LockLayer() keys to change the active keymap.
|
|
||||||
* keeping NUM and FN consistent and accessible on all layers
|
|
||||||
*
|
|
||||||
* The PROG key is special, since it is how you indicate to the board that you
|
|
||||||
* want to flash the firmware. However, it can be remapped to a regular key.
|
|
||||||
* When the keyboard boots, it first looks to see whether the PROG key is held
|
|
||||||
* down; if it is, it simply awaits further flashing instructions. If it is
|
|
||||||
* not, it continues loading the rest of the firmware and the keyboard
|
|
||||||
* functions normally, with whatever binding you have set to PROG. More detail
|
|
||||||
* here: https://community.keyboard.io/t/how-the-prog-key-gets-you-into-the-bootloader/506/8
|
|
||||||
*
|
|
||||||
* The "keymaps" data structure is a list of the keymaps compiled into the firmware.
|
|
||||||
* The order of keymaps in the list is important, as the ShiftToLayer(#) and LockLayer(#)
|
|
||||||
* macros switch to key layers based on this list.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
|
|
||||||
* A key defined as 'ShiftToLayer(FUNCTION)' will switch to FUNCTION while held.
|
|
||||||
* Similarly, a key defined as 'LockLayer(NUMPAD)' will switch to NUMPAD when tapped.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Layers are "0-indexed" -- That is the first one is layer 0. The second one is layer 1.
|
|
||||||
* The third one is layer 2.
|
|
||||||
* This 'enum' lets us use names like QWERTY, FUNCTION, and NUMPAD in place of
|
|
||||||
* the numbers 0, 1 and 2.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
enum {
|
|
||||||
PRIMARY,
|
|
||||||
// NUMPAD,
|
|
||||||
FUNCTION,
|
|
||||||
ETC,
|
|
||||||
}; // layers
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To change your keyboard's layout from QWERTY to DVORAK or COLEMAK, comment out the line
|
|
||||||
*
|
|
||||||
* #define PRIMARY_KEYMAP_QWERTY
|
|
||||||
*
|
|
||||||
* by changing it to
|
|
||||||
*
|
|
||||||
* // #define PRIMARY_KEYMAP_QWERTY
|
|
||||||
*
|
|
||||||
* Then uncomment the line corresponding to the layout you want to use.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// #define PRIMARY_KEYMAP_QWERTY
|
|
||||||
// #define PRIMARY_KEYMAP_DVORAK
|
|
||||||
// #define PRIMARY_KEYMAP_COLEMAK
|
|
||||||
#define PRIMARY_KEYMAP_CUSTOM
|
|
||||||
|
|
||||||
|
|
||||||
/* This comment temporarily turns off astyle's indent enforcement
|
|
||||||
* so we can make the keymaps actually resemble the physical key layout better
|
|
||||||
*/
|
|
||||||
// clang-format off
|
|
||||||
|
|
||||||
KEYMAPS(
|
|
||||||
|
|
||||||
[PRIMARY] = KEYMAP_STACKED
|
|
||||||
(___, ___, ___, ___, ___, ___, Key_LEDEffectNext,
|
|
||||||
___, Key_Q, Key_W, Key_D, Key_F, Key_K, Key_Tab,
|
|
||||||
___, Key_A, Key_S, Key_E, Key_T, Key_G,
|
|
||||||
Key_Backtick, Key_Z, Key_X, Key_C, Key_V, Key_B, LCTRL(LALT(Key_LeftGui)),
|
|
||||||
GUI_T(Tab), ALT_T(Backspace), CTL_T(Escape), Key_LeftShift,
|
|
||||||
ShiftToLayer(FUNCTION),
|
|
||||||
|
|
||||||
M(MACRO_ANY), ___, Key_LeftArrow, Key_DownArrow, Key_UpArrow, Key_RightArrow, ___,
|
|
||||||
Consumer_VolumeIncrement, Key_J, Key_U, Key_R, Key_L, Key_Semicolon, Key_Backslash,
|
|
||||||
Key_Y, Key_N, Key_I, Key_O, Key_H, Key_Quote,
|
|
||||||
Consumer_VolumeDecrement, Key_P, Key_M, Key_Comma, Key_Period, Key_Slash, ___,
|
|
||||||
Key_RightShift, ALT_T(Enter), Key_Spacebar, GUI_T(Tab),
|
|
||||||
ShiftToLayer(FUNCTION)),
|
|
||||||
|
|
||||||
[FUNCTION] = KEYMAP_STACKED
|
|
||||||
(___, Key_F1, Key_F2, Key_F3, Key_F4, Key_F5, ___,
|
|
||||||
___, LSHIFT(Key_1), LSHIFT(Key_2), LSHIFT(Key_LeftBracket), LSHIFT(Key_RightBracket), LSHIFT(Key_Backslash), ___,
|
|
||||||
LSHIFT(Key_7), LSHIFT(Key_3), LSHIFT(Key_4), LSHIFT(Key_9), LSHIFT(Key_0), Key_Backslash,
|
|
||||||
LSHIFT(Key_Backtick), LSHIFT(Key_5), LSHIFT(Key_6), Key_LeftBracket, Key_RightBracket, LSHIFT(Key_8), ___,
|
|
||||||
___, Key_Delete, ___, ___,
|
|
||||||
___,
|
|
||||||
|
|
||||||
___, Key_F6, Key_F7, Key_F8, Key_F9, Key_F10, Key_F11,
|
|
||||||
___, Key_Equals, Key_7, Key_8, Key_9, LSHIFT(Key_Equals), Key_F12,
|
|
||||||
Key_Minus, Key_4, Key_5, Key_6, Key_Quote, ___,
|
|
||||||
___, LSHIFT(Key_Minus), Key_1, Key_2, Key_3, LSHIFT(Key_Quote), ___,
|
|
||||||
___, ___, Key_Enter, Key_0,
|
|
||||||
___),
|
|
||||||
|
|
||||||
[ETC] = KEYMAP_STACKED
|
|
||||||
(___, ___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, ___,
|
|
||||||
___,
|
|
||||||
|
|
||||||
___, ___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, Key_F7, Key_F8, Key_F9, Key_Home, ___,
|
|
||||||
___, Key_F4, Key_F5, Key_F6, Key_End, ___,
|
|
||||||
___, ___, Key_F1, Key_F2, Key_F3, Key_Insert, ___,
|
|
||||||
___, ___, ___, ___,
|
|
||||||
___)
|
|
||||||
|
|
||||||
) // KEYMAPS(
|
|
||||||
|
|
||||||
/* Re-enable astyle's indent enforcement */
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
/** versionInfoMacro handles the 'firmware version info' macro
|
|
||||||
* When a key bound to the macro is pressed, this macro
|
|
||||||
* prints out the firmware build information as virtual keystrokes
|
|
||||||
*/
|
|
||||||
|
|
||||||
static void versionInfoMacro(uint8_t key_state) {
|
|
||||||
if (keyToggledOn(key_state)) {
|
|
||||||
Macros.type(PSTR("Keyboardio Model 100 - Firmware version "));
|
|
||||||
Macros.type(PSTR(KALEIDOSCOPE_FIRMWARE_VERSION));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** anyKeyMacro is used to provide the functionality of the 'Any' key.
|
|
||||||
*
|
|
||||||
* When the 'any key' macro is toggled on, a random alphanumeric key is
|
|
||||||
* selected. While the key is held, the function generates a synthetic
|
|
||||||
* keypress event repeating that randomly selected key.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
static void anyKeyMacro(KeyEvent &event) {
|
|
||||||
if (keyToggledOn(event.state)) {
|
|
||||||
event.key.setKeyCode(Key_A.getKeyCode() + (uint8_t)(millis() % 36));
|
|
||||||
event.key.setFlags(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** macroAction dispatches keymap events that are tied to a macro
|
|
||||||
to that macro. It takes two uint8_t parameters.
|
|
||||||
|
|
||||||
The first is the macro being called (the entry in the 'enum' earlier in this file).
|
|
||||||
The second is the state of the keyswitch. You can use the keyswitch state to figure out
|
|
||||||
if the key has just been toggled on, is currently pressed or if it's just been released.
|
|
||||||
|
|
||||||
The 'switch' statement should have a 'case' for each entry of the macro enum.
|
|
||||||
Each 'case' statement should call out to a function to handle the macro in question.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) {
|
|
||||||
switch (macro_id) {
|
|
||||||
|
|
||||||
case MACRO_VERSION_INFO:
|
|
||||||
versionInfoMacro(event.state);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MACRO_ANY:
|
|
||||||
anyKeyMacro(event);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return MACRO_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// These 'solid' color effect definitions define a rainbow of
|
|
||||||
// LED color modes calibrated to draw 500mA or less on the
|
|
||||||
// Keyboardio Model 100.
|
|
||||||
|
|
||||||
|
|
||||||
static kaleidoscope::plugin::LEDSolidColor solidRed(160, 0, 0);
|
|
||||||
static kaleidoscope::plugin::LEDSolidColor solidOrange(140, 70, 0);
|
|
||||||
static kaleidoscope::plugin::LEDSolidColor solidYellow(130, 100, 0);
|
|
||||||
static kaleidoscope::plugin::LEDSolidColor solidGreen(0, 160, 0);
|
|
||||||
static kaleidoscope::plugin::LEDSolidColor solidBlue(0, 70, 130);
|
|
||||||
static kaleidoscope::plugin::LEDSolidColor solidIndigo(0, 0, 170);
|
|
||||||
static kaleidoscope::plugin::LEDSolidColor solidViolet(130, 0, 120);
|
|
||||||
|
|
||||||
/** toggleLedsOnSuspendResume toggles the LEDs off when the host goes to sleep,
|
|
||||||
* and turns them back on when it wakes up.
|
|
||||||
*/
|
|
||||||
void toggleLedsOnSuspendResume(kaleidoscope::plugin::HostPowerManagement::Event event) {
|
|
||||||
switch (event) {
|
|
||||||
case kaleidoscope::plugin::HostPowerManagement::Suspend:
|
|
||||||
LEDControl.disable();
|
|
||||||
break;
|
|
||||||
case kaleidoscope::plugin::HostPowerManagement::Resume:
|
|
||||||
LEDControl.enable();
|
|
||||||
break;
|
|
||||||
case kaleidoscope::plugin::HostPowerManagement::Sleep:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** hostPowerManagementEventHandler dispatches power management events (suspend,
|
|
||||||
* resume, and sleep) to other functions that perform action based on these
|
|
||||||
* events.
|
|
||||||
*/
|
|
||||||
void hostPowerManagementEventHandler(kaleidoscope::plugin::HostPowerManagement::Event event) {
|
|
||||||
toggleLedsOnSuspendResume(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** This 'enum' is a list of all the magic combos used by the Model 100's
|
|
||||||
* firmware The names aren't particularly important. What is important is that
|
|
||||||
* each is unique.
|
|
||||||
*
|
|
||||||
* These are the names of your magic combos. They will be used by the
|
|
||||||
* `USE_MAGIC_COMBOS` call below.
|
|
||||||
*/
|
|
||||||
enum {
|
|
||||||
// Toggle between Boot (6-key rollover; for BIOSes and early boot) and NKRO
|
|
||||||
// mode.
|
|
||||||
COMBO_TOGGLE_NKRO_MODE,
|
|
||||||
// Enter test mode
|
|
||||||
COMBO_ENTER_TEST_MODE
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Wrappers, to be used by MagicCombo. **/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This simply toggles the keyboard protocol via USBQuirks, and wraps it within
|
|
||||||
* a function with an unused argument, to match what MagicCombo expects.
|
|
||||||
*/
|
|
||||||
static void toggleKeyboardProtocol(uint8_t combo_index) {
|
|
||||||
USBQuirks.toggleKeyboardProtocol();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles between using the built-in keymap, and the EEPROM-stored one.
|
|
||||||
*/
|
|
||||||
static void toggleKeymapSource(uint8_t combo_index) {
|
|
||||||
if (Layer.getKey == Layer.getKeyFromPROGMEM) {
|
|
||||||
Layer.getKey = EEPROMKeymap.getKey;
|
|
||||||
} else {
|
|
||||||
Layer.getKey = Layer.getKeyFromPROGMEM;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This enters the hardware test mode
|
|
||||||
*/
|
|
||||||
static void enterHardwareTestMode(uint8_t combo_index) {
|
|
||||||
HardwareTestMode.runTests();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** Magic combo list, a list of key combo and action pairs the firmware should
|
|
||||||
* recognise.
|
|
||||||
*/
|
|
||||||
USE_MAGIC_COMBOS({.action = toggleKeyboardProtocol,
|
|
||||||
// Left Fn + Esc + Shift
|
|
||||||
.keys = {R3C6, R2C6, R3C7}},
|
|
||||||
{.action = enterHardwareTestMode,
|
|
||||||
// Left Fn + Prog + LED
|
|
||||||
.keys = {R3C6, R0C0, R0C6}},
|
|
||||||
{.action = toggleKeymapSource,
|
|
||||||
// Left Fn + Prog + Shift
|
|
||||||
.keys = {R3C6, R0C0, R3C7}});
|
|
||||||
|
|
||||||
// First, tell Kaleidoscope which plugins you want to use.
|
|
||||||
// The order can be important. For example, LED effects are
|
|
||||||
// added in the order they're listed here.
|
|
||||||
KALEIDOSCOPE_INIT_PLUGINS(
|
|
||||||
// The EEPROMSettings & EEPROMKeymap plugins make it possible to have an
|
|
||||||
// editable keymap in EEPROM.
|
|
||||||
EEPROMSettings,
|
|
||||||
EEPROMKeymap,
|
|
||||||
|
|
||||||
// SpaceCadet can turn your shifts into parens on tap, while keeping them as
|
|
||||||
// Shifts when held. SpaceCadetConfig lets Chrysalis configure some aspects of
|
|
||||||
// the plugin.
|
|
||||||
// SpaceCadet,
|
|
||||||
// SpaceCadetConfig,
|
|
||||||
|
|
||||||
// Focus allows bi-directional communication with the host, and is the
|
|
||||||
// interface through which the keymap in EEPROM can be edited.
|
|
||||||
Focus,
|
|
||||||
|
|
||||||
// FocusSettingsCommand adds a few Focus commands, intended to aid in
|
|
||||||
// changing some settings of the keyboard, such as the default layer (via the
|
|
||||||
// `settings.defaultLayer` command)
|
|
||||||
FocusSettingsCommand,
|
|
||||||
|
|
||||||
// FocusEEPROMCommand adds a set of Focus commands, which are very helpful in
|
|
||||||
// both debugging, and in backing up one's EEPROM contents.
|
|
||||||
FocusEEPROMCommand,
|
|
||||||
|
|
||||||
// The boot greeting effect pulses the LED button for 10 seconds after the
|
|
||||||
// keyboard is first connected
|
|
||||||
BootGreetingEffect,
|
|
||||||
|
|
||||||
// The hardware test mode, which can be invoked by tapping Prog, LED and the
|
|
||||||
// left Fn button at the same time.
|
|
||||||
HardwareTestMode,
|
|
||||||
|
|
||||||
// LEDControl provides support for other LED modes
|
|
||||||
LEDControl,
|
|
||||||
|
|
||||||
// We start with the LED effect that turns off all the LEDs.
|
|
||||||
LEDOff,
|
|
||||||
|
|
||||||
// The rainbow effect changes the color of all of the keyboard's keys at the same time
|
|
||||||
// running through all the colors of the rainbow.
|
|
||||||
LEDRainbowEffect,
|
|
||||||
|
|
||||||
// The rainbow wave effect lights up your keyboard with all the colors of a rainbow
|
|
||||||
// and slowly moves the rainbow across your keyboard
|
|
||||||
LEDRainbowWaveEffect,
|
|
||||||
|
|
||||||
// The chase effect follows the adventure of a blue pixel which chases a red pixel across
|
|
||||||
// your keyboard. Spoiler: the blue pixel never catches the red pixel
|
|
||||||
LEDChaseEffect,
|
|
||||||
|
|
||||||
// These static effects turn your keyboard's LEDs a variety of colors
|
|
||||||
solidRed,
|
|
||||||
solidOrange,
|
|
||||||
solidYellow,
|
|
||||||
solidGreen,
|
|
||||||
solidBlue,
|
|
||||||
solidIndigo,
|
|
||||||
solidViolet,
|
|
||||||
|
|
||||||
// The breathe effect slowly pulses all of the LEDs on your keyboard
|
|
||||||
LEDBreatheEffect,
|
|
||||||
|
|
||||||
// The AlphaSquare effect prints each character you type, using your
|
|
||||||
// keyboard's LEDs as a display
|
|
||||||
AlphaSquareEffect,
|
|
||||||
|
|
||||||
// The stalker effect lights up the keys you've pressed recently
|
|
||||||
StalkerEffect,
|
|
||||||
|
|
||||||
// The LED Palette Theme plugin provides a shared palette for other plugins,
|
|
||||||
// like Colormap below
|
|
||||||
LEDPaletteTheme,
|
|
||||||
|
|
||||||
// The Colormap effect makes it possible to set up per-layer colormaps
|
|
||||||
ColormapEffect,
|
|
||||||
|
|
||||||
// The numpad plugin is responsible for lighting up the 'numpad' mode
|
|
||||||
// with a custom LED effect
|
|
||||||
// NumPad,
|
|
||||||
|
|
||||||
// The macros plugin adds support for macros
|
|
||||||
Macros,
|
|
||||||
|
|
||||||
// The MouseKeys plugin lets you add keys to your keymap which move the mouse.
|
|
||||||
// MouseKeys,
|
|
||||||
|
|
||||||
// The HostPowerManagement plugin allows us to turn LEDs off when then host
|
|
||||||
// goes to sleep, and resume them when it wakes up.
|
|
||||||
HostPowerManagement,
|
|
||||||
|
|
||||||
// The MagicCombo plugin lets you use key combinations to trigger custom
|
|
||||||
// actions - a bit like Macros, but triggered by pressing multiple keys at the
|
|
||||||
// same time.
|
|
||||||
MagicCombo,
|
|
||||||
|
|
||||||
// The USBQuirks plugin lets you do some things with USB that we aren't
|
|
||||||
// comfortable - or able - to do automatically, but can be useful
|
|
||||||
// nevertheless. Such as toggling the key report protocol between Boot (used
|
|
||||||
// by BIOSes) and Report (NKRO).
|
|
||||||
USBQuirks,
|
|
||||||
|
|
||||||
// The Qukeys plugin enables the "Secondary action" functionality in
|
|
||||||
// Chrysalis. Keys with secondary actions will have their primary action
|
|
||||||
// performed when tapped, but the secondary action when held.
|
|
||||||
Qukeys,
|
|
||||||
|
|
||||||
// Enables the "Sticky" behavior for modifiers, and the "Layer shift when
|
|
||||||
// held" functionality for layer keys.
|
|
||||||
OneShot,
|
|
||||||
EscapeOneShot,
|
|
||||||
EscapeOneShotConfig,
|
|
||||||
|
|
||||||
// Turns LEDs off after a configurable amount of idle time.
|
|
||||||
IdleLEDs,
|
|
||||||
PersistentIdleLEDs,
|
|
||||||
|
|
||||||
// Enables dynamic, Chrysalis-editable macros.
|
|
||||||
DynamicMacros,
|
|
||||||
|
|
||||||
// The FirmwareVersion plugin lets Chrysalis query the version of the firmware
|
|
||||||
// programmatically.
|
|
||||||
FirmwareVersion,
|
|
||||||
|
|
||||||
// The LayerNames plugin allows Chrysalis to display - and edit - custom layer
|
|
||||||
// names, to be shown instead of the default indexes.
|
|
||||||
LayerNames,
|
|
||||||
|
|
||||||
// Enables setting, saving (via Chrysalis), and restoring (on boot) the
|
|
||||||
// default LED mode.
|
|
||||||
DefaultLEDModeConfig
|
|
||||||
|
|
||||||
// Enables the GeminiPR Stenography protocol. Unused by default, but with the
|
|
||||||
// plugin enabled, it becomes configurable - and then usable - via Chrysalis.
|
|
||||||
//GeminiPR
|
|
||||||
);
|
|
||||||
|
|
||||||
/** The 'setup' function is one of the two standard Arduino sketch functions.
|
|
||||||
* It's called when your keyboard first powers up. This is where you set up
|
|
||||||
* Kaleidoscope and any plugins.
|
|
||||||
*/
|
|
||||||
void setup() {
|
|
||||||
// First, call Kaleidoscope's internal setup function
|
|
||||||
Kaleidoscope.setup();
|
|
||||||
|
|
||||||
// Set the hue of the boot greeting effect to something that will result in a
|
|
||||||
// nice green color.
|
|
||||||
BootGreetingEffect.hue = 85;
|
|
||||||
|
|
||||||
// While we hope to improve this in the future, the NumPad plugin
|
|
||||||
// needs to be explicitly told which keymap layer is your numpad layer
|
|
||||||
// NumPad.numPadLayer = NUMPAD;
|
|
||||||
|
|
||||||
// We configure the AlphaSquare effect to use RED letters
|
|
||||||
AlphaSquare.color = CRGB(255, 0, 0);
|
|
||||||
|
|
||||||
// We set the brightness of the rainbow effects to 150 (on a scale of 0-255)
|
|
||||||
// This draws more than 500mA, but looks much nicer than a dimmer effect
|
|
||||||
LEDRainbowEffect.brightness(255);
|
|
||||||
LEDRainbowWaveEffect.brightness(255);
|
|
||||||
|
|
||||||
// Set the action key the test mode should listen for to Left Fn
|
|
||||||
HardwareTestMode.setActionKey(R3C6);
|
|
||||||
|
|
||||||
// The LED Stalker mode has a few effects. The one we like is called
|
|
||||||
// 'BlazingTrail'. For details on other options, see
|
|
||||||
// https://github.com/keyboardio/Kaleidoscope/blob/master/docs/plugins/LED-Stalker.md
|
|
||||||
StalkerEffect.variant = STALKER(BlazingTrail);
|
|
||||||
|
|
||||||
// To make the keymap editable without flashing new firmware, we store
|
|
||||||
// additional layers in EEPROM. For now, we reserve space for eight layers. If
|
|
||||||
// one wants to use these layers, just set the default layer to one in EEPROM,
|
|
||||||
// by using the `settings.defaultLayer` Focus command, or by using the
|
|
||||||
// `keymap.onlyCustom` command to use EEPROM layers only.
|
|
||||||
EEPROMKeymap.setup(8);
|
|
||||||
|
|
||||||
// We need to tell the Colormap plugin how many layers we want to have custom
|
|
||||||
// maps for. To make things simple, we set it to eight layers, which is how
|
|
||||||
// many editable layers we have (see above).
|
|
||||||
ColormapEffect.max_layers(8);
|
|
||||||
|
|
||||||
// For Dynamic Macros, we need to reserve storage space for the editable
|
|
||||||
// macros. A kilobyte is a reasonable default.
|
|
||||||
DynamicMacros.reserve_storage(1024);
|
|
||||||
|
|
||||||
// If there's a default layer set in EEPROM, we should set that as the default
|
|
||||||
// here.
|
|
||||||
Layer.move(EEPROMSettings.default_layer());
|
|
||||||
|
|
||||||
// To avoid any surprises, SpaceCadet is turned off by default. However, it
|
|
||||||
// can be permanently enabled via Chrysalis, so we should only disable it if
|
|
||||||
// no configuration exists.
|
|
||||||
// SpaceCadetConfig.disableSpaceCadetIfUnconfigured();
|
|
||||||
// SpaceCadet.disable();
|
|
||||||
|
|
||||||
// Editable layer names are stored in EEPROM too, and we reserve 16 bytes per
|
|
||||||
// layer for them. We need one extra byte per layer for bookkeeping, so we
|
|
||||||
// reserve 17 / layer in total.
|
|
||||||
LayerNames.reserve_storage(17 * 8);
|
|
||||||
|
|
||||||
// Unless configured otherwise with Chrysalis, we want to make sure that the
|
|
||||||
// firmware starts with LED effects off. This avoids over-taxing devices that
|
|
||||||
// don't have a lot of power to share with USB devices
|
|
||||||
DefaultLEDModeConfig.activateLEDModeIfUnconfigured(&LEDOff);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** loop is the second of the standard Arduino sketch functions.
|
|
||||||
* As you might expect, it runs in a loop, never exiting.
|
|
||||||
*
|
|
||||||
* For Kaleidoscope-based keyboard firmware, you usually just want to
|
|
||||||
* call Kaleidoscope.loop(); and not do anything custom here.
|
|
||||||
*/
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
Kaleidoscope.loop();
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"cpu": {
|
|
||||||
"fqbn": "keyboardio:gd32:keyboardio_model_100",
|
|
||||||
"port": ""
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,2 @@
|
|||||||
|
DEFAULT_SKETCH="Atreus"
|
||||||
|
BOARD="keyboardio_atreus"
|
@ -0,0 +1,50 @@
|
|||||||
|
# This makefile for a Kaleidoscope sketch pulls in all the targets
|
||||||
|
# required to build the example
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$(info "mebbe Adding $(KALEIDOSCOPE_DIR)")
|
||||||
|
ifneq ($(KALEIDOSCOPE_DIR),)
|
||||||
|
search_path += $(KALEIDOSCOPE_DIR)
|
||||||
|
$(info "Adding $(KALEIDOSCOPE_DIR)")
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifneq ($(ARDUINO_DIRECTORIES_USER),)
|
||||||
|
search_path += $(ARDUINO_DIRECTORIES_USER)/hardware/keyboardio/avr/libraries/Kaleidoscope
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(shell uname -s),Darwin)
|
||||||
|
search_path += $(HOME)/Documents/Arduino/hardware/keyboardio/avr/libraries/Kaleidoscope
|
||||||
|
else
|
||||||
|
search_path += $(HOME)/Arduino/hardware/keyboardio/avr/libraries/Kaleidoscope
|
||||||
|
endif
|
||||||
|
|
||||||
|
sketch_makefile := etc/makefiles/sketch.mk
|
||||||
|
|
||||||
|
$(foreach candidate, $(search_path), $(if $(wildcard $(candidate)/$(sketch_makefile)), $(eval ks_dir ?= $(candidate))))
|
||||||
|
|
||||||
|
ifneq ($(ks_dir),)
|
||||||
|
|
||||||
|
$(info Using Kaleidoscope from $(ks_dir))
|
||||||
|
|
||||||
|
export KALEIDOSCOPE_DIR := $(ks_dir)
|
||||||
|
include $(ks_dir)/$(sketch_makefile)
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
$(info I can't find your Kaleidoscope installation.)
|
||||||
|
$(info )
|
||||||
|
$(info I tried looking in:)
|
||||||
|
$(info )
|
||||||
|
$(foreach candidate, $(search_path), $(info $(candidate)))
|
||||||
|
$(info )
|
||||||
|
$(info The easiest way to fix this is to set the 'KALEIDOSCOPE_DIR' environment)
|
||||||
|
$(info variable to the location of your Kaleidoscope directory.)
|
||||||
|
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
null-target:
|
||||||
|
$(info You should never see this message)
|
||||||
|
@:
|
@ -0,0 +1,146 @@
|
|||||||
|
/* -*- mode: c++ -*-
|
||||||
|
* Atreus -- Chrysalis-enabled Sketch for the Keyboardio Atreus
|
||||||
|
* Copyright (C) 2018, 2019 Keyboard.io, Inc
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef BUILD_INFORMATION
|
||||||
|
#define BUILD_INFORMATION "locally built"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "Kaleidoscope.h"
|
||||||
|
#include "Kaleidoscope-EEPROM-Settings.h"
|
||||||
|
#include "Kaleidoscope-EEPROM-Keymap.h"
|
||||||
|
#include "Kaleidoscope-FocusSerial.h"
|
||||||
|
#include "Kaleidoscope-Macros.h"
|
||||||
|
#include "Kaleidoscope-MouseKeys.h"
|
||||||
|
#include "Kaleidoscope-OneShot.h"
|
||||||
|
#include "Kaleidoscope-Qukeys.h"
|
||||||
|
#include "Kaleidoscope-SpaceCadet.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#define MO(n) ShiftToLayer(n)
|
||||||
|
#define TG(n) LockLayer(n)
|
||||||
|
|
||||||
|
enum {
|
||||||
|
MACRO_QWERTY,
|
||||||
|
MACRO_VERSION_INFO
|
||||||
|
};
|
||||||
|
|
||||||
|
#define Key_Exclamation LSHIFT(Key_1)
|
||||||
|
#define Key_At LSHIFT(Key_2)
|
||||||
|
#define Key_Hash LSHIFT(Key_3)
|
||||||
|
#define Key_Dollar LSHIFT(Key_4)
|
||||||
|
#define Key_Percent LSHIFT(Key_5)
|
||||||
|
#define Key_Caret LSHIFT(Key_6)
|
||||||
|
#define Key_And LSHIFT(Key_7)
|
||||||
|
#define Key_Star LSHIFT(Key_8)
|
||||||
|
#define Key_Plus LSHIFT(Key_Equals)
|
||||||
|
|
||||||
|
enum {
|
||||||
|
QWERTY,
|
||||||
|
FUN,
|
||||||
|
UPPER
|
||||||
|
};
|
||||||
|
|
||||||
|
/* *INDENT-OFF* */
|
||||||
|
KEYMAPS(
|
||||||
|
[QWERTY] = KEYMAP_STACKED
|
||||||
|
(
|
||||||
|
Key_Q ,Key_W ,Key_E ,Key_R ,Key_T
|
||||||
|
,Key_A ,Key_S ,Key_D ,Key_F ,Key_G
|
||||||
|
,Key_Z ,Key_X ,Key_C ,Key_V ,Key_B, Key_Backtick
|
||||||
|
,Key_Esc ,Key_Tab ,Key_LeftGui ,Key_LeftShift ,Key_Backspace ,Key_LeftControl
|
||||||
|
|
||||||
|
,Key_Y ,Key_U ,Key_I ,Key_O ,Key_P
|
||||||
|
,Key_H ,Key_J ,Key_K ,Key_L ,Key_Semicolon
|
||||||
|
,Key_Backslash,Key_N ,Key_M ,Key_Comma ,Key_Period ,Key_Slash
|
||||||
|
,Key_LeftAlt ,Key_Space ,MO(FUN) ,Key_Minus ,Key_Quote ,Key_Enter
|
||||||
|
),
|
||||||
|
|
||||||
|
[FUN] = KEYMAP_STACKED
|
||||||
|
(
|
||||||
|
Key_Exclamation ,Key_At ,Key_UpArrow ,Key_Dollar ,Key_Percent
|
||||||
|
,Key_LeftParen ,Key_LeftArrow ,Key_DownArrow ,Key_RightArrow ,Key_RightParen
|
||||||
|
,Key_LeftBracket ,Key_RightBracket ,Key_Hash ,Key_LeftCurlyBracket ,Key_RightCurlyBracket ,Key_Caret
|
||||||
|
,TG(UPPER) ,Key_Insert ,Key_LeftGui ,Key_LeftShift ,Key_Delete ,Key_LeftControl
|
||||||
|
|
||||||
|
,Key_PageUp ,Key_7 ,Key_8 ,Key_9 ,Key_Backspace
|
||||||
|
,Key_PageDown ,Key_4 ,Key_5 ,Key_6 ,___
|
||||||
|
,Key_And ,Key_Star ,Key_1 ,Key_2 ,Key_3 ,Key_Plus
|
||||||
|
,Key_LeftAlt ,Key_Space ,___ ,Key_Period ,Key_0 ,Key_Equals
|
||||||
|
),
|
||||||
|
|
||||||
|
[UPPER] = KEYMAP_STACKED
|
||||||
|
(
|
||||||
|
Key_Insert ,Key_Home ,Key_UpArrow ,Key_End ,Key_PageUp
|
||||||
|
,Key_Delete ,Key_LeftArrow ,Key_DownArrow ,Key_RightArrow ,Key_PageDown
|
||||||
|
,M(MACRO_VERSION_INFO) ,Consumer_VolumeIncrement ,XXX ,XXX ,___ ,___
|
||||||
|
,MoveToLayer(QWERTY) ,Consumer_VolumeDecrement ,___ ,___ ,___ ,___
|
||||||
|
|
||||||
|
,Key_UpArrow ,Key_F7 ,Key_F8 ,Key_F9 ,Key_F10
|
||||||
|
,Key_DownArrow ,Key_F4 ,Key_F5 ,Key_F6 ,Key_F11
|
||||||
|
,___ ,XXX ,Key_F1 ,Key_F2 ,Key_F3 ,Key_F12
|
||||||
|
,___ ,___ ,MoveToLayer(QWERTY) ,Key_PrintScreen ,Key_ScrollLock ,Consumer_PlaySlashPause
|
||||||
|
)
|
||||||
|
)
|
||||||
|
/* *INDENT-ON* */
|
||||||
|
|
||||||
|
KALEIDOSCOPE_INIT_PLUGINS(
|
||||||
|
EEPROMSettings,
|
||||||
|
EEPROMKeymap,
|
||||||
|
Focus,
|
||||||
|
FocusEEPROMCommand,
|
||||||
|
FocusSettingsCommand,
|
||||||
|
Qukeys,
|
||||||
|
SpaceCadet,
|
||||||
|
OneShot,
|
||||||
|
Macros,
|
||||||
|
MouseKeys
|
||||||
|
);
|
||||||
|
|
||||||
|
const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) {
|
||||||
|
switch (macroIndex) {
|
||||||
|
case MACRO_QWERTY:
|
||||||
|
// This macro is currently unused, but is kept around for compatibility
|
||||||
|
// reasons. We used to use it in place of `MoveToLayer(QWERTY)`, but no
|
||||||
|
// longer do. We keep it so that if someone still has the old layout with
|
||||||
|
// the macro in EEPROM, it will keep working after a firmware update.
|
||||||
|
Layer.move(QWERTY);
|
||||||
|
break;
|
||||||
|
case MACRO_VERSION_INFO:
|
||||||
|
if (keyToggledOn(keyState)) {
|
||||||
|
Macros.type(PSTR("Keyboardio Atreus - Kaleidoscope "));
|
||||||
|
Macros.type(PSTR(BUILD_INFORMATION));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MACRO_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Kaleidoscope.setup();
|
||||||
|
SpaceCadet.disable();
|
||||||
|
EEPROMKeymap.setup(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
Kaleidoscope.loop();
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"cpu": {
|
||||||
|
"fqbn": "kaleidoscope:samd:kbio_metro_m4",
|
||||||
|
"port": ""
|
||||||
|
}
|
||||||
|
}
|
@ -1,79 +0,0 @@
|
|||||||
/* -*- mode: c++ -*-
|
|
||||||
* AppSwitcher -- A Kaleidoscope Example
|
|
||||||
* Copyright (C) 2021 Keyboardio, 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define KALEIDOSCOPE_HOSTOS_GUESSER 1
|
|
||||||
|
|
||||||
#include <Kaleidoscope-HostOS.h>
|
|
||||||
|
|
||||||
#include "AppSwitcher.h"
|
|
||||||
|
|
||||||
namespace kaleidoscope {
|
|
||||||
namespace plugin {
|
|
||||||
|
|
||||||
EventHandlerResult AppSwitcher::onKeyEvent(KeyEvent &event) {
|
|
||||||
// Ignore all key releases
|
|
||||||
if (keyToggledOff(event.state))
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
|
|
||||||
if (event.key == AppSwitcher_Next || event.key == AppSwitcher_Prev) {
|
|
||||||
bool add_shift_flag = false;
|
|
||||||
if (event.key == AppSwitcher_Prev) {
|
|
||||||
add_shift_flag = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For good measure:
|
|
||||||
event.state |= INJECTED;
|
|
||||||
|
|
||||||
// If AppSwitcher was not already active, hold its modifier first.
|
|
||||||
if (!active_addr_.isValid()) {
|
|
||||||
if (::HostOS.os() == hostos::MACOS) {
|
|
||||||
event.key = Key_LeftGui;
|
|
||||||
} else {
|
|
||||||
event.key = Key_LeftAlt;
|
|
||||||
}
|
|
||||||
Runtime.handleKeyEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear the event's key address so we don't clobber the modifier.
|
|
||||||
event.addr.clear();
|
|
||||||
event.key = Key_Tab;
|
|
||||||
if (add_shift_flag)
|
|
||||||
event.key.setFlags(SHIFT_HELD);
|
|
||||||
// Press tab
|
|
||||||
Runtime.handleKeyEvent(event);
|
|
||||||
// Change state to release; this will get processed when we return OK below.
|
|
||||||
event.state = WAS_PRESSED | INJECTED;
|
|
||||||
} else if (active_addr_.isValid()) {
|
|
||||||
// If any non-AppSwitcher key is pressed while AppSwitcher is active, that
|
|
||||||
// will close AppSwitcher instead of processing that keypress. We mask the
|
|
||||||
// address of the key that closed AppSwitcher so that its release doesn't
|
|
||||||
// have any effect. Then we turn the event for that key's press into an
|
|
||||||
// event for the release of the AppSwitcher's modifier key.
|
|
||||||
live_keys.mask(event.addr);
|
|
||||||
event.addr = active_addr_;
|
|
||||||
event.state = WAS_PRESSED | INJECTED;
|
|
||||||
event.key = live_keys[event.addr];
|
|
||||||
// Turn off AppSwitcher:
|
|
||||||
active_addr_.clear();
|
|
||||||
}
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace plugin
|
|
||||||
} // namespace kaleidoscope
|
|
||||||
|
|
||||||
kaleidoscope::plugin::AppSwitcher AppSwitcher;
|
|
@ -1,42 +0,0 @@
|
|||||||
/* -*- mode: c++ -*-
|
|
||||||
* AppSwitcher -- A Kaleidoscope Example
|
|
||||||
* Copyright (C) 2021 Keyboardio, 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Kaleidoscope.h>
|
|
||||||
#include "Kaleidoscope-Ranges.h"
|
|
||||||
|
|
||||||
constexpr Key AppSwitcher_Next{kaleidoscope::ranges::SAFE_START};
|
|
||||||
constexpr uint16_t _prev_val = AppSwitcher_Next.getRaw() + 1;
|
|
||||||
constexpr Key AppSwitcher_Prev{_prev_val};
|
|
||||||
|
|
||||||
namespace kaleidoscope {
|
|
||||||
namespace plugin {
|
|
||||||
|
|
||||||
class AppSwitcher : public kaleidoscope::Plugin {
|
|
||||||
|
|
||||||
public:
|
|
||||||
EventHandlerResult onKeyEvent(KeyEvent &event);
|
|
||||||
|
|
||||||
private:
|
|
||||||
KeyAddr active_addr_ = KeyAddr::none();
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace plugin
|
|
||||||
} // namespace kaleidoscope
|
|
||||||
|
|
||||||
extern kaleidoscope::plugin::AppSwitcher AppSwitcher;
|
|
@ -0,0 +1,65 @@
|
|||||||
|
/* -*- mode: c++ -*-
|
||||||
|
* AppSwitcher -- A Kaleidoscope Example
|
||||||
|
* Copyright (C) 2016-2018 Keyboardio, 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define KALEIDOSCOPE_HOSTOS_GUESSER 1
|
||||||
|
|
||||||
|
#include <Kaleidoscope-HostOS.h>
|
||||||
|
|
||||||
|
#include "Macros.h"
|
||||||
|
|
||||||
|
namespace H = kaleidoscope::hostos;
|
||||||
|
|
||||||
|
static bool appSwitchActive = false;
|
||||||
|
|
||||||
|
const macro_t *macroAppSwitch(uint8_t keyState) {
|
||||||
|
appSwitchActive = true;
|
||||||
|
|
||||||
|
// Key was just pressed, or is being held
|
||||||
|
if (keyIsPressed(keyState)) {
|
||||||
|
if (HostOS.os() == H::OSX)
|
||||||
|
return MACRO(Dr(Key_LeftGui), D(Tab));
|
||||||
|
else
|
||||||
|
return MACRO(Dr(Key_LeftAlt), D(Tab));
|
||||||
|
}
|
||||||
|
// Key was just released
|
||||||
|
if (keyToggledOff(keyState)) {
|
||||||
|
if (HostOS.os() == H::OSX)
|
||||||
|
return MACRO(U(Tab), Dr(Key_LeftGui));
|
||||||
|
else
|
||||||
|
return MACRO(U(Tab), Dr(Key_LeftAlt));
|
||||||
|
}
|
||||||
|
// otherwise we do nothing
|
||||||
|
return MACRO_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const macro_t *macroAppCancel(uint8_t keyState) {
|
||||||
|
if (keyToggledOn(keyState))
|
||||||
|
appSwitchActive = false;
|
||||||
|
return MACRO_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void macroAppSwitchLoop() {
|
||||||
|
Key mod = Key_LeftAlt;
|
||||||
|
|
||||||
|
if (HostOS.os() == H::OSX)
|
||||||
|
mod = Key_LeftGui;
|
||||||
|
|
||||||
|
// if appSwitchActive is true, we continue holding Alt.
|
||||||
|
if (appSwitchActive) {
|
||||||
|
handleKeyswitchEvent(mod, UnknownKeyswitchLocation, IS_PRESSED);
|
||||||
|
}
|
||||||
|
}
|
@ -1,76 +0,0 @@
|
|||||||
/* -*- mode: c++ -*-
|
|
||||||
* Kaleidoscope - A Kaleidoscope example
|
|
||||||
* Copyright (C) 2016-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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define DEBUG_SERIAL false
|
|
||||||
|
|
||||||
#include <Kaleidoscope.h>
|
|
||||||
#include <Kaleidoscope-MouseKeys.h>
|
|
||||||
|
|
||||||
enum {
|
|
||||||
PRIMARY,
|
|
||||||
MOUSEKEYS,
|
|
||||||
};
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
KEYMAPS(
|
|
||||||
[PRIMARY] = KEYMAP_STACKED
|
|
||||||
(Key_NoKey, Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey,
|
|
||||||
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
|
|
||||||
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
|
|
||||||
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
|
|
||||||
|
|
||||||
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
|
|
||||||
ShiftToLayer(MOUSEKEYS),
|
|
||||||
|
|
||||||
Key_skip, Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip,
|
|
||||||
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
|
|
||||||
Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote,
|
|
||||||
Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
|
|
||||||
|
|
||||||
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
|
|
||||||
LockLayer(MOUSEKEYS)
|
|
||||||
),
|
|
||||||
|
|
||||||
[MOUSEKEYS] = KEYMAP_STACKED
|
|
||||||
(___, ___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, ___, Key_mouseWarpNW, Key_mouseWarpNE, ___,
|
|
||||||
___, ___, ___, ___, Key_mouseWarpSW, Key_mouseWarpSE,
|
|
||||||
___, ___, Key_mouseBtnL, Key_mouseBtnM, Key_mouseBtnR, ___, ___,
|
|
||||||
___, ___, ___, ___,
|
|
||||||
___,
|
|
||||||
|
|
||||||
___, ___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, Key_mouseUp, ___, ___, ___,
|
|
||||||
___, Key_mouseUp, Key_mouseDn, Key_mouseR, ___, ___,
|
|
||||||
___, ___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, ___,
|
|
||||||
___)
|
|
||||||
)
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
|
|
||||||
KALEIDOSCOPE_INIT_PLUGINS(MouseKeys);
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Kaleidoscope.setup();
|
|
||||||
MouseKeys.setSpeedLimit(100);
|
|
||||||
MouseKeys.setWarpGridSize(MOUSE_WARP_GRID_2X2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
Kaleidoscope.loop();
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"cpu": {
|
|
||||||
"fqbn": "keyboardio:avr:model01",
|
|
||||||
"port": ""
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
/* -*- mode: c++ -*-
|
|
||||||
* ShiftBlocker -- A Kaleidoscope Example
|
|
||||||
* Copyright (C) 2016-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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "Kaleidoscope.h"
|
|
||||||
#include "Kaleidoscope-Macros.h"
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
KEYMAPS(
|
|
||||||
[0] = KEYMAP_STACKED
|
|
||||||
(
|
|
||||||
Key_NoKey, Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey,
|
|
||||||
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
|
|
||||||
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
|
|
||||||
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
|
|
||||||
|
|
||||||
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
|
|
||||||
M(0),
|
|
||||||
|
|
||||||
Key_skip, Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip,
|
|
||||||
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
|
|
||||||
Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote,
|
|
||||||
Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
|
|
||||||
|
|
||||||
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
|
|
||||||
M(0)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
namespace kaleidoscope {
|
|
||||||
namespace plugin {
|
|
||||||
|
|
||||||
// When activated, this plugin will suppress any `Shift` key (including modifier
|
|
||||||
// combos with `Shift`) before it's added to the HID report.
|
|
||||||
class ShiftBlocker : public Plugin {
|
|
||||||
|
|
||||||
public:
|
|
||||||
EventHandlerResult onAddToReport(Key key) {
|
|
||||||
if (active_ && key.isKeyboardShift())
|
|
||||||
return EventHandlerResult::ABORT;
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void enable() {
|
|
||||||
active_ = true;
|
|
||||||
}
|
|
||||||
void disable() {
|
|
||||||
active_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool active_{false};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace plugin
|
|
||||||
} // namespace kaleidoscope
|
|
||||||
|
|
||||||
kaleidoscope::plugin::ShiftBlocker ShiftBlocker;
|
|
||||||
|
|
||||||
const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) {
|
|
||||||
if (keyToggledOn(event.state)) {
|
|
||||||
switch (macro_id) {
|
|
||||||
case 0:
|
|
||||||
// First, enable ShiftBlocker to suppress any held `Shift` key(s).
|
|
||||||
ShiftBlocker.enable();
|
|
||||||
// Tap `AltGr` + `7` to activate the grave accent dead key.
|
|
||||||
Macros.tap(RALT(Key_7));
|
|
||||||
// Disable ShiftBlocker so it won't affect the `E` event.
|
|
||||||
ShiftBlocker.disable();
|
|
||||||
// Change the Macros key into a plain `E` key before its press event is
|
|
||||||
// processed.
|
|
||||||
event.key = Key_E;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return MACRO_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
KALEIDOSCOPE_INIT_PLUGINS(Macros,
|
|
||||||
ShiftBlocker);
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Kaleidoscope.setup();
|
|
||||||
// Uncomment to manually set the OS, as Kaleidoscope will not autodetect it.
|
|
||||||
// (Possible values are in HostOS.h.)
|
|
||||||
// HostOS.os(kaleidoscope::hostos::LINUX);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
Kaleidoscope.loop();
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"cpu": {
|
|
||||||
"fqbn": "keyboardio:avr:model01",
|
|
||||||
"port": ""
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
// -*- mode: c++ -*-
|
|
||||||
|
|
||||||
#include <Kaleidoscope.h>
|
|
||||||
|
|
||||||
#include <Kaleidoscope-AutoShift.h>
|
|
||||||
#include <Kaleidoscope-EEPROM-Settings.h>
|
|
||||||
#include <Kaleidoscope-EEPROM-Keymap.h>
|
|
||||||
#include <Kaleidoscope-FocusSerial.h>
|
|
||||||
#include <Kaleidoscope-Macros.h>
|
|
||||||
|
|
||||||
enum {
|
|
||||||
TOGGLE_AUTOSHIFT,
|
|
||||||
};
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
KEYMAPS(
|
|
||||||
[0] = KEYMAP_STACKED
|
|
||||||
(
|
|
||||||
Key_NoKey, Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey,
|
|
||||||
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
|
|
||||||
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
|
|
||||||
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
|
|
||||||
|
|
||||||
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
|
|
||||||
XXX,
|
|
||||||
|
|
||||||
M(TOGGLE_AUTOSHIFT), Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip,
|
|
||||||
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
|
|
||||||
Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote,
|
|
||||||
Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
|
|
||||||
|
|
||||||
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
|
|
||||||
XXX
|
|
||||||
),
|
|
||||||
)
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
// Defining a macro (on the "any" key: see above) to turn AutoShift on and off
|
|
||||||
const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) {
|
|
||||||
switch (macro_id) {
|
|
||||||
case TOGGLE_AUTOSHIFT:
|
|
||||||
if (keyToggledOn(event.state))
|
|
||||||
AutoShift.toggle();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return MACRO_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This sketch uses the AutoShiftConfig plugin, which enables run-time
|
|
||||||
// configuration of AutoShift configuration settings. All of the plugins marked
|
|
||||||
// "for AutoShiftConfig" are optional; AutoShift itself will work without them.
|
|
||||||
KALEIDOSCOPE_INIT_PLUGINS(
|
|
||||||
EEPROMSettings, // for AutoShiftConfig
|
|
||||||
EEPROMKeymap, // for AutoShiftConfig
|
|
||||||
Focus, // for AutoShiftConfig
|
|
||||||
FocusEEPROMCommand, // for AutoShiftConfig
|
|
||||||
FocusSettingsCommand, // for AutoShiftConfig
|
|
||||||
AutoShift,
|
|
||||||
AutoShiftConfig, // for AutoShiftConfig
|
|
||||||
Macros // for toggle AutoShift Macro
|
|
||||||
);
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
// Enable AutoShift for letter keys and number keys only:
|
|
||||||
AutoShift.setEnabled(AutoShift.letterKeys() | AutoShift.numberKeys());
|
|
||||||
// Add symbol keys to the enabled categories:
|
|
||||||
AutoShift.enable(AutoShift.symbolKeys());
|
|
||||||
// Set the AutoShift long-press time to 150ms:
|
|
||||||
AutoShift.setTimeout(150);
|
|
||||||
// Start with AutoShift turned off:
|
|
||||||
AutoShift.disable();
|
|
||||||
|
|
||||||
Kaleidoscope.setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
Kaleidoscope.loop();
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"cpu": {
|
|
||||||
"fqbn": "keyboardio:avr:model01",
|
|
||||||
"port": ""
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
// -*- mode: c++ -*-
|
|
||||||
|
|
||||||
#include <Kaleidoscope.h>
|
|
||||||
|
|
||||||
#include <Kaleidoscope-CharShift.h>
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
KEYMAPS(
|
|
||||||
[0] = KEYMAP_STACKED
|
|
||||||
(
|
|
||||||
XXX, Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey,
|
|
||||||
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
|
|
||||||
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
|
|
||||||
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
|
|
||||||
|
|
||||||
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
|
|
||||||
XXX,
|
|
||||||
|
|
||||||
XXX, Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip,
|
|
||||||
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
|
|
||||||
Key_H, Key_J, Key_K, Key_L, CS(2), Key_Quote,
|
|
||||||
Key_skip, Key_N, Key_M, CS(0), CS(1), Key_Slash, Key_Minus,
|
|
||||||
|
|
||||||
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
|
|
||||||
XXX
|
|
||||||
),
|
|
||||||
)
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
KALEIDOSCOPE_INIT_PLUGINS(CharShift);
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
CS_KEYS(
|
|
||||||
kaleidoscope::plugin::CharShift::KeyPair(Key_Comma, Key_Semicolon), // CS(0)
|
|
||||||
kaleidoscope::plugin::CharShift::KeyPair(Key_Period, LSHIFT(Key_Semicolon)), // CS(1)
|
|
||||||
kaleidoscope::plugin::CharShift::KeyPair(LSHIFT(Key_Comma), LSHIFT(Key_Period)), // CS(2)
|
|
||||||
);
|
|
||||||
Kaleidoscope.setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
Kaleidoscope.loop();
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"cpu": {
|
|
||||||
"fqbn": "keyboardio:avr:model01",
|
|
||||||
"port": ""
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,200 +0,0 @@
|
|||||||
/* -*- mode: c++ -*-
|
|
||||||
* Kaleidoscope-LeaderPrefix -- Prefix arg for Leader plugin
|
|
||||||
* Copyright (C) 2021 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <Kaleidoscope.h>
|
|
||||||
#include <Kaleidoscope-Leader.h>
|
|
||||||
#include <Kaleidoscope-MacroSupport.h>
|
|
||||||
|
|
||||||
#include <Kaleidoscope-Ranges.h>
|
|
||||||
#include "kaleidoscope/KeyEventTracker.h"
|
|
||||||
#include "kaleidoscope/LiveKeys.h"
|
|
||||||
#include "kaleidoscope/plugin.h"
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
KEYMAPS(
|
|
||||||
[0] = KEYMAP_STACKED
|
|
||||||
(Key_NoKey, Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey,
|
|
||||||
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
|
|
||||||
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
|
|
||||||
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
|
|
||||||
|
|
||||||
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
|
|
||||||
LEAD(0),
|
|
||||||
|
|
||||||
Key_skip, Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip,
|
|
||||||
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
|
|
||||||
Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote,
|
|
||||||
Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
|
|
||||||
|
|
||||||
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
|
|
||||||
LEAD(0)),
|
|
||||||
)
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
namespace kaleidoscope {
|
|
||||||
namespace plugin {
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
/// Plugin to supply a numeric prefix argument to Leader key functions
|
|
||||||
///
|
|
||||||
/// This plugin lets the user type a numeric prefix after a Leader key is
|
|
||||||
/// pressed, but before the rest of the Leader sequence is begun, storing the
|
|
||||||
/// "prefix argument" and making it available to functions called from the
|
|
||||||
/// leader dictionary. LeaderPrefix allows us to define keys other than the
|
|
||||||
/// ones on the number row to be interpreted as the "digit" keys, because
|
|
||||||
/// whatever we use will need to be accessed without a layer change.
|
|
||||||
class LeaderPrefix : public Plugin {
|
|
||||||
public:
|
|
||||||
// We need to define `onKeyswitchEvent()` instead of `onKeyEvent()` because we
|
|
||||||
// need to intercept events before Leader sees them, and the Leader plugin
|
|
||||||
// uses the former.
|
|
||||||
EventHandlerResult onKeyswitchEvent(KeyEvent &event) {
|
|
||||||
// Every `onKeyswitchEvent()` function should begin with this to prevent
|
|
||||||
// re-processing events that it has already seen.
|
|
||||||
if (event_tracker_.shouldIgnore(event))
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
|
|
||||||
// `Active` means that we're actively building the prefix argument. If the
|
|
||||||
// plugin is not active, we're looking for a Leader key toggling on.
|
|
||||||
if (!active_) {
|
|
||||||
if (keyToggledOn(event.state) && isLeaderKey(event.key)) {
|
|
||||||
// A Leader key toggled on, so we set our state to "active", and set the
|
|
||||||
// arg value to zero.
|
|
||||||
active_ = true;
|
|
||||||
leader_arg_ = 0;
|
|
||||||
}
|
|
||||||
// Whether or not the plugin just became active, there's nothing more to
|
|
||||||
// do for this event.
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The plugin is "active", so we're looking for a "digit" key that just
|
|
||||||
// toggled on.
|
|
||||||
if (keyToggledOn(event.state)) {
|
|
||||||
// We search our array of digit keys to find one that matches the event.
|
|
||||||
// These "digit keys" are defined by their `KeyAddr` because they're
|
|
||||||
// probably independent of keymap and layer, and because a `KeyAddr` only
|
|
||||||
// takes one byte, whereas a `Key` takes two.
|
|
||||||
for (uint8_t i{0}; i < 10; ++i) {
|
|
||||||
if (digit_addrs_[i] == event.addr) {
|
|
||||||
// We found a match, which means that one of our "digit keys" toggled
|
|
||||||
// on. If this happens more than once, the user is typing a number
|
|
||||||
// with multiple digits, so we multiply the current value by ten
|
|
||||||
// before adding the new digit to the total.
|
|
||||||
leader_arg_ *= 10;
|
|
||||||
leader_arg_ += i;
|
|
||||||
// Next, we mask the key that was just pressed, so that nothing will
|
|
||||||
// happen when it is released.
|
|
||||||
live_keys.mask(event.addr);
|
|
||||||
// We return `ABORT` so that no other plugins (i.e. Leader) will see
|
|
||||||
// this keypress event.
|
|
||||||
return EventHandlerResult::ABORT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// No match was found, so the key that toggled on was not one of our "digit
|
|
||||||
// keys". Presumably, this is the first key in the Leader sequence that is
|
|
||||||
// being typed. We leave the prefix argument at its current value so that
|
|
||||||
// it will still be set when the sequence is finished, and allow the event
|
|
||||||
// to pass through to the next plugin (i.e. Leader).
|
|
||||||
active_ = false;
|
|
||||||
return EventHandlerResult::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t arg() const {
|
|
||||||
return leader_arg_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
// The "digit keys": these are the keys on the number row of the Model01.
|
|
||||||
KeyAddr digit_addrs_[10] = {
|
|
||||||
KeyAddr(0, 14),
|
|
||||||
KeyAddr(0, 1),
|
|
||||||
KeyAddr(0, 2),
|
|
||||||
KeyAddr(0, 3),
|
|
||||||
KeyAddr(0, 4),
|
|
||||||
KeyAddr(0, 5),
|
|
||||||
KeyAddr(0, 10),
|
|
||||||
KeyAddr(0, 11),
|
|
||||||
KeyAddr(0, 12),
|
|
||||||
KeyAddr(0, 13),
|
|
||||||
};
|
|
||||||
|
|
||||||
// This event tracker is necessary to prevent re-processing events. Any
|
|
||||||
// plugin that defines `onKeyswitchEvent()` should use one.
|
|
||||||
KeyEventTracker event_tracker_;
|
|
||||||
|
|
||||||
// The current state of the plugin. It determines whether we're looking for a
|
|
||||||
// Leader keypress or building a prefix argument.
|
|
||||||
bool active_{false};
|
|
||||||
|
|
||||||
// The prefix argument itself.
|
|
||||||
uint16_t leader_arg_{0};
|
|
||||||
|
|
||||||
// Leader should probably provide this test, but since it doesn't, we add it
|
|
||||||
// here to determine if a key is a Leader key.
|
|
||||||
bool isLeaderKey(Key key) {
|
|
||||||
return (key >= ranges::LEAD_FIRST && key <= ranges::LEAD_LAST);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace plugin
|
|
||||||
} // namespace kaleidoscope
|
|
||||||
|
|
||||||
// This creates our plugin object.
|
|
||||||
kaleidoscope::plugin::LeaderPrefix LeaderPrefix;
|
|
||||||
|
|
||||||
auto &serial_port = Kaleidoscope.serialPort();
|
|
||||||
|
|
||||||
static void leaderTestX(uint8_t seq_index) {
|
|
||||||
serial_port.println(F("leaderTestX"));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void leaderTestXX(uint8_t seq_index) {
|
|
||||||
serial_port.println(F("leaderTestXX"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// This demonstrates how to use the prefix argument in a Leader function. In
|
|
||||||
// this case, our function just types as many `x` characters as specified by the
|
|
||||||
// prefix arg.
|
|
||||||
void leaderTestPrefix(uint8_t seq_index) {
|
|
||||||
// Read the prefix argument into a temporary variable:
|
|
||||||
uint8_t prefix_arg = LeaderPrefix.arg();
|
|
||||||
// Use a Macros helper function to tap the `X` key repeatedly.
|
|
||||||
while (prefix_arg-- > 0)
|
|
||||||
MacroSupport.tap(Key_X);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const kaleidoscope::plugin::Leader::dictionary_t leader_dictionary[] PROGMEM =
|
|
||||||
LEADER_DICT({LEADER_SEQ(LEAD(0), Key_X), leaderTestX},
|
|
||||||
{LEADER_SEQ(LEAD(0), Key_X, Key_X), leaderTestXX},
|
|
||||||
{LEADER_SEQ(LEAD(0), Key_Z), leaderTestPrefix});
|
|
||||||
|
|
||||||
// The order matters here; LeaderPrefix won't work unless it precedes Leader in
|
|
||||||
// this list. If there are other plugins in the list, these two should ideally
|
|
||||||
// be next to each other, but that's not necessary.
|
|
||||||
KALEIDOSCOPE_INIT_PLUGINS(LeaderPrefix, Leader);
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Kaleidoscope.setup();
|
|
||||||
|
|
||||||
Leader.dictionary = leader_dictionary;
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
Kaleidoscope.loop();
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue