Compare commits
5 Commits
main
...
pointing-d
Author | SHA1 | Date |
---|---|---|
Gergely Nagy | bb95be4861 | 4 years ago |
Gergely Nagy | 4c8bc6ffe1 | 4 years ago |
Gergely Nagy | 0403463a6b | 4 years ago |
Gergely Nagy | a7379aca0b | 4 years ago |
Gergely Nagy | 9ebab0fb52 | 4 years ago |
@ -1,47 +0,0 @@
|
|||||||
# -*- mode: yaml -*-
|
|
||||||
---
|
|
||||||
BasedOnStyle: Google
|
|
||||||
---
|
|
||||||
Language: Cpp
|
|
||||||
|
|
||||||
AlignConsecutiveAssignments: Consecutive
|
|
||||||
## clang-format-15
|
|
||||||
# AlignConsecutiveAssignments:
|
|
||||||
# Enabled: true
|
|
||||||
# AlignCompound: true
|
|
||||||
# PadOperators: true
|
|
||||||
AlignConsecutiveDeclarations: None
|
|
||||||
AlignConsecutiveMacros: AcrossEmptyLines
|
|
||||||
AlignEscapedNewlines: Right
|
|
||||||
AllowShortBlocksOnASingleLine: Empty
|
|
||||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
|
||||||
AllowShortFunctionsOnASingleLine: Inline
|
|
||||||
AllowShortLoopsOnASingleLine: true
|
|
||||||
AttributeMacros:
|
|
||||||
- __attribute__((weak))
|
|
||||||
- __attribute__((always_inline))
|
|
||||||
- __attribute__((noinline))
|
|
||||||
- __attribute__((packed))
|
|
||||||
- __attribute__((optimize(3)))
|
|
||||||
- __attribute__((unused))
|
|
||||||
BinPackArguments: false
|
|
||||||
BinPackParameters: false
|
|
||||||
# BraceWrapping:
|
|
||||||
# SplitEmptyFunction: false
|
|
||||||
# SplitEmptyRecord: true
|
|
||||||
# SplitEmptyNamespace: true
|
|
||||||
# BreakBeforeBraces: Custom
|
|
||||||
ColumnLimit: 0
|
|
||||||
ConstructorInitializerIndentWidth: 2
|
|
||||||
ContinuationIndentWidth: 2
|
|
||||||
DerivePointerAlignment: false
|
|
||||||
FixNamespaceComments: true
|
|
||||||
IndentCaseLabels: false
|
|
||||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
|
||||||
MaxEmptyLinesToKeep: 2
|
|
||||||
# PackConstructorInitializers: CurrentLine
|
|
||||||
PointerAlignment: Right
|
|
||||||
# ReferenceAlignment: Right
|
|
||||||
ReflowComments: false
|
|
||||||
SortIncludes: false
|
|
||||||
SpaceAfterTemplateKeyword: false
|
|
@ -1,9 +0,0 @@
|
|||||||
set noparent
|
|
||||||
|
|
||||||
extensions=cpp,h,ino
|
|
||||||
|
|
||||||
filter=-build/include
|
|
||||||
filter=-legal/copyright
|
|
||||||
filter=-readability/namespace
|
|
||||||
filter=-runtime/references
|
|
||||||
filter=-whitespace
|
|
@ -1,9 +0,0 @@
|
|||||||
set noparent
|
|
||||||
|
|
||||||
extensions=cpp,h,ino
|
|
||||||
|
|
||||||
filter=-build/include
|
|
||||||
filter=-legal/copyright
|
|
||||||
filter=-readability/namespace
|
|
||||||
filter=-runtime/references
|
|
||||||
filter=-whitespace/line_length
|
|
@ -1,14 +0,0 @@
|
|||||||
examples
|
|
||||||
src/Kaleidoscope.h
|
|
||||||
src/Kaleidoscope-LEDControl.h
|
|
||||||
src/kaleidoscope/HIDTables.h
|
|
||||||
src/kaleidoscope/driver/bootloader/gd32/Base.h
|
|
||||||
src/kaleidoscope/driver/led/Base.h
|
|
||||||
src/kaleidoscope/driver/led/WS2812.h
|
|
||||||
src/kaleidoscope/driver/led/ws2812/config.h
|
|
||||||
src/kaleidoscope/driver/mcu/GD32.h
|
|
||||||
src/kaleidoscope/driver/storage/GD32Flash.h
|
|
||||||
plugins/Kaleidoscope-FirmwareDump/**
|
|
||||||
plugins/Kaleidoscope-HostOS/src/kaleidoscope/plugin/HostOS.h
|
|
||||||
plugins/Kaleidoscope-Hardware-EZ-ErgoDox/src/kaleidoscope/device/ez/ErgoDox/i2cmaster.h
|
|
||||||
testing/googletest
|
|
@ -0,0 +1,3 @@
|
|||||||
|
# -*- mode: sh -*-
|
||||||
|
|
||||||
|
DEFAULT_SKETCH=Kaleidoscope
|
@ -1,13 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
: "${KALEIDOSCOPE_DIR:=$(pwd)}"
|
|
||||||
cd "${KALEIDOSCOPE_DIR}" || exit 1
|
|
||||||
|
|
||||||
ERROR_COUNT=0
|
|
||||||
|
|
||||||
while read -r SCRIPT; do
|
|
||||||
shellcheck "${SCRIPT}"
|
|
||||||
(( ERROR_COUNT += $? ))
|
|
||||||
done < <(grep -E -n -r -l '(env (ba)?sh)|(/bin/(ba)?sh)' "${KALEIDOSCOPE_DIR}/bin")
|
|
||||||
|
|
||||||
exit $ERROR_COUNT
|
|
@ -1,110 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# Copyright (c) 2022 Michael Richters <gedankenexperimenter@gmail.com>
|
|
||||||
|
|
||||||
# This is free and unencumbered software released into the public domain.
|
|
||||||
|
|
||||||
# Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
||||||
# distribute this software, either in source code form or as a compiled
|
|
||||||
# binary, for any purpose, commercial or non-commercial, and by any
|
|
||||||
# means.
|
|
||||||
|
|
||||||
# In jurisdictions that recognize copyright laws, the author or authors
|
|
||||||
# of this software dedicate any and all copyright interest in the
|
|
||||||
# software to the public domain. We make this dedication for the benefit
|
|
||||||
# of the public at large and to the detriment of our heirs and
|
|
||||||
# successors. We intend this dedication to be an overt act of
|
|
||||||
# relinquishment in perpetuity of all present and future rights to this
|
|
||||||
# software under copyright law.
|
|
||||||
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
||||||
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
||||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
||||||
# OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
# For more information, please refer to <http://unlicense.org/>
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
"""Utilities shared by Kaleidoscope code maintenance tools."""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from contextlib import contextmanager
|
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
@contextmanager
|
|
||||||
def cwd(temp_wd):
|
|
||||||
"""Execute code in a different working directory
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
`temp_wd` (`str`): The name of a directory to (temporarily) change to
|
|
||||||
|
|
||||||
Change the current working directory, then automatically restore the previous working
|
|
||||||
directory when done. Invoke `cwd()` like this:
|
|
||||||
```py
|
|
||||||
with cwd(temp_wd):
|
|
||||||
...
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
old_wd = os.getcwd()
|
|
||||||
os.chdir(temp_wd)
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
os.chdir(old_wd)
|
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
def split_on_newlines(string):
|
|
||||||
"""Split the string using newlines as the separator."""
|
|
||||||
return string.splitlines()
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
def split_on_nulls(string):
|
|
||||||
"""Split the input string using NULL characters as the separator."""
|
|
||||||
targets = [_ for _ in string.split('\0') if _ != '']
|
|
||||||
return targets or []
|
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
def setup_logging(loglevel):
|
|
||||||
"""Set up basic logging."""
|
|
||||||
logformat = "%(message)s"
|
|
||||||
logging.basicConfig(
|
|
||||||
level=loglevel,
|
|
||||||
stream=sys.stdout,
|
|
||||||
format=logformat,
|
|
||||||
datefmt="",
|
|
||||||
)
|
|
||||||
return logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
def check_git_diff():
|
|
||||||
"""Check for unstaged changes with `git diff`
|
|
||||||
|
|
||||||
Returns: a list of the names of files with unstaged changes
|
|
||||||
|
|
||||||
This check isn't perfect, because it can give false positives (if there are unrelated
|
|
||||||
unstaged changes).
|
|
||||||
"""
|
|
||||||
git_diff_cmd = ['git', 'diff', '-z', '--exit-code', '--name-only']
|
|
||||||
|
|
||||||
changed_files = []
|
|
||||||
proc = subprocess.run(git_diff_cmd, capture_output=True)
|
|
||||||
if proc.returncode != 0:
|
|
||||||
changed_files = split_on_nulls(proc.stdout.decode('utf-8'))
|
|
||||||
return changed_files
|
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(1)
|
|
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
|
use warnings;
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
#
|
||||||
|
# Scan all USB devices to find the Model 01's modem device number.
|
||||||
|
#
|
||||||
|
my @output = qx(/usr/sbin/usbconfig show_ifdrv);
|
||||||
|
my $serial_port_number;
|
||||||
|
|
||||||
|
foreach my $line (@output) {
|
||||||
|
chomp $line;
|
||||||
|
|
||||||
|
next unless $line =~ m/umodem(\d+):.*Keyboardio Model 01/;
|
||||||
|
$serial_port_number = $1;
|
||||||
|
}
|
||||||
|
|
||||||
|
die "Can't find Model 01" unless defined($serial_port_number);
|
||||||
|
|
||||||
|
my $serial_port_name = "/dev/cuaU$serial_port_number";
|
||||||
|
die "Missing serial port at $serial_port_name" unless -e $serial_port_name;
|
||||||
|
print "$serial_port_name\n";
|
||||||
|
exit 0;
|
@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
# find-device-port-linux-udev - Kaleidoscope helper tool
|
||||||
|
# Copyright (C) 2017-2018 Keyboard.io, Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation, version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use warnings;
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
use FindBin qw{$Bin};
|
||||||
|
|
||||||
|
die "Usage: $0 VID PID [-v]\n" unless @ARGV >= 2;
|
||||||
|
|
||||||
|
my $vid = shift @ARGV;
|
||||||
|
my $pid = shift @ARGV;
|
||||||
|
my $verbose = shift @ARGV if (@ARGV);
|
||||||
|
my $prefix = '/dev/serial/by-id/';
|
||||||
|
my @paths = `ls $prefix`;
|
||||||
|
my %devices;
|
||||||
|
my @log;
|
||||||
|
|
||||||
|
sub debug {
|
||||||
|
if ($verbose) {
|
||||||
|
print STDERR @_;
|
||||||
|
} else {
|
||||||
|
push @log, @_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub print_warning {
|
||||||
|
print STDERR @_;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
debug "Looking for USB device with vid=$vid and pid=$pid\n";
|
||||||
|
|
||||||
|
for my $path (@paths) {
|
||||||
|
chomp($path);
|
||||||
|
debug "Examining $path\n";
|
||||||
|
debug " not symlink\n" unless -l $prefix . $path;
|
||||||
|
next unless -l $prefix . $path;
|
||||||
|
my @data = `udevadm info -q property --name=${prefix}${path}`;
|
||||||
|
for my $line (@data) {
|
||||||
|
chomp($line);
|
||||||
|
my ( $key, $val ) = split( /=/, $line, 2 );
|
||||||
|
$devices{$path}{$key} = $val;
|
||||||
|
}
|
||||||
|
if ( hex $devices{$path}{'ID_VENDOR_ID'} != hex $vid ) {
|
||||||
|
debug " ID_VENDOR_ID $devices{$path}{'ID_VENDOR_ID'} != $vid\n";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
if ( hex $devices{$path}{'ID_MODEL_ID'} != hex $pid ) {
|
||||||
|
debug " ID_MODEL_ID $devices{$path}{'ID_MODEL_ID'} != $pid\n";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug " Found keyboard!\n";
|
||||||
|
|
||||||
|
if ( $devices{$path}{'ID_MM_DEVICE_IGNORE'} ) {
|
||||||
|
debug " ID_MM_DEVICE_IGNORE is set - good!\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $devices{$path}{'ID_MM_CANDIDATE'}) {
|
||||||
|
my $rules = "$Bin/../etc/60-kaleidoscope.rules";
|
||||||
|
print_warning <<EOWARN
|
||||||
|
|
||||||
|
WARNING: your udev rules are currently configured to suggest
|
||||||
|
that your keyboard is suitable for use by ModemManager. This
|
||||||
|
means that there is a risk of ModemManager interfering. To avoid
|
||||||
|
this, copy
|
||||||
|
|
||||||
|
$rules
|
||||||
|
|
||||||
|
to /etc/udev/rules.d
|
||||||
|
EOWARN
|
||||||
|
}
|
||||||
|
|
||||||
|
print $devices{$path}{DEVNAME};
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#debug("ERROR: I couldn't find a USB device matching the keyboard's USB Vendor and Device IDs\n");
|
||||||
|
#print_warning(join("\n",@log));
|
@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
|
# Based on listArduinos.pl from https://github.com/todbot/usbSearch (License: MIT)
|
||||||
|
# Original (C) 2012, Tod E. Kurt, http://todbot.com/blog/
|
||||||
|
# This version by Michael Richters <gedankenexperimenter@gmail.com> and Jesse Vincent <jesse@keyboard.io>
|
||||||
|
|
||||||
|
use warnings;
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
my $vid = shift @ARGV;
|
||||||
|
my $pid = shift @ARGV;
|
||||||
|
|
||||||
|
if (!defined $vid || !defined $pid) {
|
||||||
|
die "$0 has two required parameters, VID and PID";
|
||||||
|
}
|
||||||
|
|
||||||
|
# ioreg might be more machine-readable than system_profiler, but I haven't been able to
|
||||||
|
# get it to produce useful output
|
||||||
|
my @output = qx(/usr/sbin/system_profiler SPUSBDataType 2> /dev/null);
|
||||||
|
|
||||||
|
my $serial = "";
|
||||||
|
my $location = "";
|
||||||
|
|
||||||
|
my $output = join('', @output);
|
||||||
|
|
||||||
|
my @stanzas = split(/\n\n/, $output);
|
||||||
|
foreach my $stanza (@stanzas) {
|
||||||
|
if ($stanza =~ /Product ID: ${pid}/ && $stanza =~ /Vendor ID: ${vid}/) {
|
||||||
|
if ($stanza =~ /Serial Number: (.*?)$/m) {
|
||||||
|
$serial = $1;
|
||||||
|
}
|
||||||
|
if ($stanza =~ /Location ID: (.*?)$/m) {
|
||||||
|
$location = $1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($serial) {
|
||||||
|
try_for_raw_serialnum($serial);
|
||||||
|
}
|
||||||
|
if ($location) {
|
||||||
|
try_for_location_id($location);
|
||||||
|
}
|
||||||
|
if ($serial) {
|
||||||
|
try_for_sn_prefix($serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub try_for_raw_serialnum {
|
||||||
|
my $sn = shift;
|
||||||
|
|
||||||
|
my $serial_port_name = "/dev/cu.usbmodem" . $sn;
|
||||||
|
exit_with_port_if_exists($serial_port_name);
|
||||||
|
|
||||||
|
# High Sierra sometimes has a mismatch between the serial number and the device
|
||||||
|
# filename. I'm not sure why, but system_profiler has a serial number ending in "E",
|
||||||
|
# whereas the device filename ends in "1". In fact, when I change HID.getShortName()
|
||||||
|
# to return "kbio02", the final character is replaced with a "1".
|
||||||
|
|
||||||
|
if ($serial_port_name =~ /\d$/) {
|
||||||
|
chop $serial_port_name;
|
||||||
|
exit_with_port_if_exists($serial_port_name . "1");
|
||||||
|
} else {
|
||||||
|
# If the serial port name doesn't end with a digit, try -appending- rather than replacing
|
||||||
|
# the last character of the port name
|
||||||
|
exit_with_port_if_exists($serial_port_name . "1");
|
||||||
|
|
||||||
|
# and if that didn't work, try replacing the last character with a "1" anyway.
|
||||||
|
# Jason Koh reports that he saw this behavior as required on Catalina in May 2020.
|
||||||
|
|
||||||
|
chop $serial_port_name;
|
||||||
|
exit_with_port_if_exists($serial_port_name . "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sub try_for_location_id {
|
||||||
|
my $location_id = shift;
|
||||||
|
|
||||||
|
# macOS truncates the string of "0"s from the right of the location id.
|
||||||
|
# Here, also, the final character is an appended "1", so if macOS ever stops doing that,
|
||||||
|
# this will need an update, as well.
|
||||||
|
if ($location_id =~ /0x(\d+?)0*\b/) {
|
||||||
|
my $loc = $1;
|
||||||
|
exit_with_port_if_exists("/dev/cu.usbmodem" . $loc . "1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub try_for_sn_prefix {
|
||||||
|
my $sn = shift;
|
||||||
|
# If macOS has appended 'E', take it off to maximise our chances of a match.
|
||||||
|
$sn =~ s/E$//;
|
||||||
|
|
||||||
|
# If none of the above tests succeeds, just list the directory and see if there are any
|
||||||
|
# files that have the device shortname that we expect:
|
||||||
|
foreach my $line (qx(ls /dev/cu.usbmodem*)) {
|
||||||
|
if ($line =~ /${sn}/) {
|
||||||
|
chomp $line;
|
||||||
|
print $line;
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub exit_with_port_if_exists {
|
||||||
|
my $serial_port_name = shift;
|
||||||
|
|
||||||
|
if (-e $serial_port_name) {
|
||||||
|
print $serial_port_name;
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
# Usage:
|
||||||
|
#
|
||||||
|
# > find-device-port-cygwin.ps1 '1209' '2300' -Format COM
|
||||||
|
# COM7
|
||||||
|
# > find-device-port-cygwin.ps1 '1209' '2300' -Format WSL
|
||||||
|
# /dev/ttyS7
|
||||||
|
# > find-device-port-cygwin.ps1 '1209' '2300' -Format Cygwin
|
||||||
|
# /dev/ttyS6
|
||||||
|
Param(
|
||||||
|
[string]$VendorID,
|
||||||
|
[string]$ProductID, # Careful; $PID is a different builtin
|
||||||
|
[ValidateSet('COM','Cygwin','WSL')][string]$Format
|
||||||
|
)
|
||||||
|
|
||||||
|
$DeviceParametersRegKey = @(Get-ChildItem -ErrorAction SilentlyContinue -Recurse 'HKLM:\SYSTEM\CurrentControlSet\Enum' |
|
||||||
|
Where-Object Name -match "VID_$VendorID&PID_$ProductID" |
|
||||||
|
Where-Object Property -eq PortName)
|
||||||
|
|
||||||
|
if ($DeviceParametersRegKey.Count -eq 0) {
|
||||||
|
throw "Could not find any devices matching VID $VendorID and PID $ProductID which were mapped to a COM port"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($DeviceParametersRegKey.Count -ge 2) {
|
||||||
|
throw "More than one devices matching VID $VendorID and PID $ProductID were found mapped to a COM port"
|
||||||
|
}
|
||||||
|
|
||||||
|
# This will be of form 'COM6'
|
||||||
|
$COMPortName = ($DeviceParametersRegKey | Get-ItemProperty).PortName
|
||||||
|
$COMPortNumber = [int]$COMPortName.Substring(3)
|
||||||
|
|
||||||
|
if ($Format -eq 'COM') {
|
||||||
|
$Output = $COMPortName
|
||||||
|
} elseif ($Format -eq 'WSL') {
|
||||||
|
$Output = "/dev/ttyS$COMPortNumber"
|
||||||
|
} elseif ($Format -eq 'Cygwin') {
|
||||||
|
$CygwinPortNumber = $COMPortNumber - 1
|
||||||
|
$Output = "/dev/ttyS$CygwinPortNumber"
|
||||||
|
}
|
||||||
|
|
||||||
|
# "-NoNewline" below is important to prevent bash from seeing an extra trailing '\r'
|
||||||
|
Write-Host -NoNewline $Output
|
@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# find-filename-conflicts - Finds cpp files with conflicting filenames
|
||||||
|
# Copyright (C) 2020 Keyboard.io, Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation, version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
## When building Kaleidoscope, the compiled object files are linked together
|
||||||
|
## into a static archive. This static archive has a very simple structure, and
|
||||||
|
## only stores filenames, not paths, not even relative ones. As such, we can't
|
||||||
|
## have files with the same name, because they will conflict, and one will
|
||||||
|
## override the other.
|
||||||
|
##
|
||||||
|
## To avoid this situation, this script will find all cpp source files (we don't
|
||||||
|
## need to care about header-only things, those do not result in an object
|
||||||
|
## file), and will comb through them to find conflicting filenames.
|
||||||
|
##
|
||||||
|
## If a conflict is found, it will print all files that share the name, and will
|
||||||
|
## exit with an error at the end. It does not exit at the first duplicate, but
|
||||||
|
## will find and print all of them.
|
||||||
|
##
|
||||||
|
## If no conflict is found, the script just prints its status message and exits
|
||||||
|
## with zero.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
FILE_LIST="$(find src -name '*.cpp' | sed -e 's,\(\(.*\)/\([^/]*\)\),\3 \1,')"
|
||||||
|
|
||||||
|
exit_code=0
|
||||||
|
|
||||||
|
echo -n "Looking for conflicting filenames... "
|
||||||
|
|
||||||
|
for f in $(echo "${FILE_LIST}" | cut -f1 -d" "); do
|
||||||
|
count=$(echo "${FILE_LIST}" | grep -c "^${f}")
|
||||||
|
if [ "$count" -gt 1 ]; then
|
||||||
|
echo >&2
|
||||||
|
echo " Conflict found for ${f}: " >&2
|
||||||
|
echo "${FILE_LIST}" | grep "${f}" | cut -d" " -f2 | sed -e 's,^, ,' >&2
|
||||||
|
exit_code=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "${exit_code}" -eq 0 ]; then
|
||||||
|
echo "done."
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit ${exit_code}
|
@ -1,96 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# Copyright (c) 2022 Michael Richters <gedankenexperimenter@gmail.com>
|
|
||||||
|
|
||||||
# This is free and unencumbered software released into the public domain.
|
|
||||||
|
|
||||||
# Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
||||||
# distribute this software, either in source code form or as a compiled
|
|
||||||
# binary, for any purpose, commercial or non-commercial, and by any
|
|
||||||
# means.
|
|
||||||
|
|
||||||
# In jurisdictions that recognize copyright laws, the author or authors
|
|
||||||
# of this software dedicate any and all copyright interest in the
|
|
||||||
# software to the public domain. We make this dedication for the benefit
|
|
||||||
# of the public at large and to the detriment of our heirs and
|
|
||||||
# successors. We intend this dedication to be an overt act of
|
|
||||||
# relinquishment in perpetuity of all present and future rights to this
|
|
||||||
# software under copyright law.
|
|
||||||
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
||||||
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
||||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
||||||
# OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
# For more information, please refer to <http://unlicense.org/>
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
"""When building Kaleidoscope, the compiled object files are linked together
|
|
||||||
into a static archive. This static archive has a very simple structure, and only
|
|
||||||
stores filenames, not paths, not even relative ones. As such, we can't have
|
|
||||||
files with the same name, because they will conflict, and one will override the
|
|
||||||
other.
|
|
||||||
|
|
||||||
To avoid this situation, this script will find all cpp source files (we don't
|
|
||||||
need to care about header-only things, those do not result in an object file),
|
|
||||||
and will comb through them to find conflicting filenames.
|
|
||||||
|
|
||||||
If a conflict is found, it will print all files that share the name, and will
|
|
||||||
exit with an error at the end. It does not exit at the first duplicate, but will
|
|
||||||
find and print all of them.
|
|
||||||
|
|
||||||
If no conflict is found, the script just prints its status message and exits
|
|
||||||
with zero."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
cpp_regex = re.compile('.*\.cpp')
|
|
||||||
|
|
||||||
|
|
||||||
def find_duplicates(root):
|
|
||||||
"""Search for files with the same basename, but in different directories in
|
|
||||||
the tree under <root>. Prints a message for each conflict found, and
|
|
||||||
returns a count of the number of non-unique basenames."""
|
|
||||||
|
|
||||||
# Search the specified tree for matching basenames:
|
|
||||||
basenames = {}
|
|
||||||
for dir_path, dirs, files in os.walk(root):
|
|
||||||
for file_name in files:
|
|
||||||
if cpp_regex.match(file_name):
|
|
||||||
if file_name not in basenames:
|
|
||||||
basenames[file_name] = []
|
|
||||||
basenames[file_name].append(dir_path)
|
|
||||||
|
|
||||||
conflict_count = 0
|
|
||||||
for file_name, dirs in basenames.items():
|
|
||||||
# Prune unique basenames from the dict:
|
|
||||||
if len(dirs) <= 1:
|
|
||||||
continue
|
|
||||||
|
|
||||||
conflict_count += 1
|
|
||||||
# Print info about basenames with conflicts:
|
|
||||||
print(f"Conflict found for file name '{file_name}':")
|
|
||||||
for root in dirs:
|
|
||||||
path = os.path.join(root, file_name)
|
|
||||||
print(f' -> {path}')
|
|
||||||
|
|
||||||
return conflict_count
|
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
|
||||||
print('Searching for conflicting filenames...')
|
|
||||||
exit_code = 0
|
|
||||||
for path in args:
|
|
||||||
exit_code += find_duplicates(path)
|
|
||||||
if exit_code != 0:
|
|
||||||
sys.exit(exit_code)
|
|
||||||
print('No filename conflicts found.')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main(sys.argv[1:])
|
|
@ -1,43 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# In case it hasn't been set, assume we're being run from the root of the local
|
|
||||||
# Kaleidoscope repository.
|
|
||||||
: "${KALEIDOSCOPE_DIR:=$(pwd)}"
|
|
||||||
|
|
||||||
# This variable is a git ref that should point to the current master branch of
|
|
||||||
# the primary Kaleidoscope repository. In most cases, this would be
|
|
||||||
# `origin/master`.
|
|
||||||
: "${KALEIDOSCOPE_MERGE_BASE:=origin/master}"
|
|
||||||
|
|
||||||
# Don't do anything if the working tree has unstaged changes, to avoid
|
|
||||||
# unintentional combining of contentful changes with formatting changes.
|
|
||||||
if ! git diff -z --exit-code --quiet; then
|
|
||||||
echo "Working tree has unstaged changes; aborting."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Run git-diff so we only run IWYU on files that differ from `master`. This
|
|
||||||
# isn't necessarily what we want, but if the current branch has been rebased, it
|
|
||||||
# shouldn't touch any extra files.
|
|
||||||
git diff -z --name-only "${KALEIDOSCOPE_MERGE_BASE}" -- src plugins \
|
|
||||||
| "${KALEIDOSCOPE_DIR}/bin/iwyu.py" -z -v
|
|
||||||
|
|
||||||
# After running it on Kaleidoscope source files, run it on the test simulator,
|
|
||||||
# which requires some additional include dirs.
|
|
||||||
git diff -z --name-only "${KALEIDOSCOPE_MERGE_BASE}" -- testing \
|
|
||||||
| "${KALEIDOSCOPE_DIR}/bin/iwyu.py" \
|
|
||||||
-z -v \
|
|
||||||
-I="${KALEIDOSCOPE_DIR}" \
|
|
||||||
-I="${KALEIDOSCOPE_DIR}/testing/googletest/googlemock/include" \
|
|
||||||
-I="${KALEIDOSCOPE_DIR}/testing/googletest/googletest/include"
|
|
||||||
|
|
||||||
# Always run clang-format after IWYU, because they have different indentation
|
|
||||||
# rules for comments added by IWYU.
|
|
||||||
git diff -z --name-only "${KALEIDOSCOPE_MERGE_BASE}" -- src plugins testing \
|
|
||||||
| "${KALEIDOSCOPE_DIR}/bin/format-code.py" \
|
|
||||||
-z -v \
|
|
||||||
--exclude-dir='testing/googletest' \
|
|
||||||
--exclude-file='generated-testcase.cpp' \
|
|
||||||
--force \
|
|
||||||
--check \
|
|
||||||
--verbose
|
|
@ -1,63 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# focus-send - Trivial Focus testing tool
|
|
||||||
# Copyright (C) 2018-2022 Keyboard.io, Inc.
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify it under
|
|
||||||
# the terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, version 3.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
OS=$(uname -s)
|
|
||||||
|
|
||||||
# igncr absorbs CR from Focus CRLF line endings
|
|
||||||
# -echo is needed because raw doesn't turn it off on Linux
|
|
||||||
STTY_ARGS="9600 raw igncr -echo"
|
|
||||||
|
|
||||||
case ${OS} in
|
|
||||||
Linux)
|
|
||||||
DEVICE="${DEVICE:-/dev/ttyACM0}"
|
|
||||||
;;
|
|
||||||
Darwin)
|
|
||||||
# bash on macOS has a bug that randomly drops serial input
|
|
||||||
if [ -n "$BASH_VERSION" ] && [ -x /bin/dash ]; then
|
|
||||||
# Prevent loop in case someone exported it
|
|
||||||
export -n BASH_VERSION
|
|
||||||
exec /bin/dash "$0" "$@"
|
|
||||||
fi
|
|
||||||
DEVICE="${DEVICE:-/dev/cu.usbmodemCkbio01E}"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Error Unknown OS : ${OS}" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Redirect prior to running stty, because macOS sometimes resets termios
|
|
||||||
# state upon last close of a terminal device.
|
|
||||||
exec < "${DEVICE}"
|
|
||||||
# shellcheck disable=SC2086 # intentional word splitting
|
|
||||||
stty $STTY_ARGS
|
|
||||||
|
|
||||||
read_reply () {
|
|
||||||
while read -r line; do
|
|
||||||
if [ "${line}" = "." ]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
echo "${line}"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Flush any invalid commands out of input buffer.
|
|
||||||
# This could happen after a failed upload.
|
|
||||||
echo ' ' > "${DEVICE}"
|
|
||||||
read_reply > /dev/null
|
|
||||||
echo "$@" >"${DEVICE}"
|
|
||||||
read_reply
|
|
@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# focus-test - Trivial Focus testing tool
|
||||||
|
# Copyright (C) 2018 Keyboard.io, Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation, version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
OS=$(uname -s)
|
||||||
|
|
||||||
|
case ${OS} in
|
||||||
|
Linux)
|
||||||
|
DEVICE="${DEVICE:-/dev/ttyACM0}"
|
||||||
|
stty -F "${DEVICE}" 9600 raw -echo
|
||||||
|
;;
|
||||||
|
Darwin)
|
||||||
|
DEVICE="${DEVICE:-/dev/cu.usbmodemCkbio01E}"
|
||||||
|
stty -f "${DEVICE}" 9600 raw -echo
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Error Unknown OS : ${OS}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
exec 3<"${DEVICE}"
|
||||||
|
echo "$@" >"${DEVICE}"
|
||||||
|
|
||||||
|
while read -r line <&3; do
|
||||||
|
line="$(echo -n "${line}" | tr -d '\r')"
|
||||||
|
if [ "${line}" == "." ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "${line}"
|
||||||
|
done
|
@ -1,275 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# Copyright (c) 2022 Michael Richters <gedankenexperimenter@gmail.com>
|
|
||||||
|
|
||||||
# This is free and unencumbered software released into the public domain.
|
|
||||||
|
|
||||||
# Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
||||||
# distribute this software, either in source code form or as a compiled
|
|
||||||
# binary, for any purpose, commercial or non-commercial, and by any
|
|
||||||
# means.
|
|
||||||
|
|
||||||
# In jurisdictions that recognize copyright laws, the author or authors
|
|
||||||
# of this software dedicate any and all copyright interest in the
|
|
||||||
# software to the public domain. We make this dedication for the benefit
|
|
||||||
# of the public at large and to the detriment of our heirs and
|
|
||||||
# successors. We intend this dedication to be an overt act of
|
|
||||||
# relinquishment in perpetuity of all present and future rights to this
|
|
||||||
# software under copyright law.
|
|
||||||
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
||||||
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
||||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
||||||
# OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
# For more information, please refer to <http://unlicense.org/>
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
"""This script runs clang-format on a Kaleidoscope repository."""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
sys.dont_write_bytecode = True
|
|
||||||
|
|
||||||
from common import check_git_diff, setup_logging, split_on_newlines, split_on_nulls
|
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
def parse_args(args):
|
|
||||||
"""Parse command line parameters
|
|
||||||
|
|
||||||
Args:
|
|
||||||
args (list[str]): command line parameters as list of strings
|
|
||||||
(for example ``["--help"]``).
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
:obj:`argparse.Namespace`: command line parameters namespace
|
|
||||||
"""
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="""
|
|
||||||
Recursively search specified directories and format source files with
|
|
||||||
clang-format. By default, it operates on Arduino C++ source files with
|
|
||||||
extensions: *.{cpp,h,hpp,inc,ino}.""")
|
|
||||||
parser.add_argument(
|
|
||||||
'-q',
|
|
||||||
'--quiet',
|
|
||||||
dest='loglevel',
|
|
||||||
action='store_const',
|
|
||||||
const=logging.ERROR,
|
|
||||||
default=logging.WARNING,
|
|
||||||
help="""
|
|
||||||
Suppress output except warnings and errors.""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-v',
|
|
||||||
'--verbose',
|
|
||||||
action='store_const',
|
|
||||||
dest='loglevel',
|
|
||||||
const=logging.INFO,
|
|
||||||
help="""
|
|
||||||
Output verbose debugging information.""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-d',
|
|
||||||
'--debug',
|
|
||||||
action='store_const',
|
|
||||||
dest='loglevel',
|
|
||||||
const=logging.DEBUG,
|
|
||||||
help="""
|
|
||||||
Save output from `include-what-you-use` for processed files beside the originals, with
|
|
||||||
a '.iwyu' suffix, for debugging purposes.""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-X',
|
|
||||||
'--exclude-dir',
|
|
||||||
action='append',
|
|
||||||
dest='exclude_dirs',
|
|
||||||
default=[],
|
|
||||||
metavar="<path>",
|
|
||||||
help="""
|
|
||||||
Exclude dir from search (path relative to the pwd)""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-x',
|
|
||||||
'--exclude-file',
|
|
||||||
action='append',
|
|
||||||
dest='exclude_files',
|
|
||||||
default=[],
|
|
||||||
metavar="<file>",
|
|
||||||
help="""
|
|
||||||
Exclude <file> (base name only, not a full path) from formatting""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-r',
|
|
||||||
'--regex',
|
|
||||||
dest='regex',
|
|
||||||
default=r'\.(cpp|h|hpp|inc|ino)$',
|
|
||||||
metavar="<regex>",
|
|
||||||
help="""
|
|
||||||
Regular expression for matching source file names""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-z',
|
|
||||||
'-0',
|
|
||||||
action='store_const',
|
|
||||||
dest='input_splitter',
|
|
||||||
const=split_on_nulls,
|
|
||||||
default=split_on_newlines,
|
|
||||||
help="""
|
|
||||||
When reading target filenames from standard input, break on NULL characters instead
|
|
||||||
of newlines.""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-f',
|
|
||||||
'--force',
|
|
||||||
action='store_true',
|
|
||||||
help="""
|
|
||||||
Format code even if there are unstaged changes""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--check',
|
|
||||||
action='store_true',
|
|
||||||
help="""
|
|
||||||
Check for changes after formatting by running `git diff --exit-code`. If there are any
|
|
||||||
changes after formatting, a non-zero exit code is returned.""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'targets',
|
|
||||||
metavar="<search_dir>",
|
|
||||||
nargs='*',
|
|
||||||
help="""
|
|
||||||
A list of files and/or directories to search for source files to format.""",
|
|
||||||
)
|
|
||||||
return parser.parse_args(args)
|
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
def main():
|
|
||||||
"""Parse command-line arguments and format source files.
|
|
||||||
"""
|
|
||||||
# Parse command-line argumets:
|
|
||||||
opts = parse_args(sys.argv[1:])
|
|
||||||
# Set up logging system:
|
|
||||||
setup_logging(opts.loglevel)
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
# Unless we've been given the `--force` option, check for unstaged changes to avoid
|
|
||||||
# clobbering any work in progress:
|
|
||||||
exit_code = 0
|
|
||||||
if not opts.force:
|
|
||||||
changed_files = check_git_diff()
|
|
||||||
if len(changed_files) > 0:
|
|
||||||
logging.error("Working tree has unstaged changes; aborting")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Locate `clang-format` executable:
|
|
||||||
clang_format_exe = os.getenv('KALEIDOSCOPE_CODE_FORMATTER')
|
|
||||||
if clang_format_exe is None:
|
|
||||||
clang_format_exe = shutil.which('clang-format')
|
|
||||||
logging.debug("Found `clang-format` executable: %s", clang_format_exe)
|
|
||||||
clang_format_cmd = [clang_format_exe, '-i']
|
|
||||||
if opts.loglevel <= logging.INFO:
|
|
||||||
clang_format_cmd.append('--verbose')
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
# Read targets from command line:
|
|
||||||
targets = opts.targets
|
|
||||||
logging.debug("CLI target parameters: %s", targets)
|
|
||||||
|
|
||||||
# If stdin is a pipe, read target filenames from it:
|
|
||||||
if not sys.stdin.isatty():
|
|
||||||
targets += opts.input_splitter(sys.stdin.read())
|
|
||||||
logging.debug("All targets: %s", targets)
|
|
||||||
|
|
||||||
# Prepare exclusion lists. The file excludes are basenames only, and the dirs get
|
|
||||||
# converted to absolute path names.
|
|
||||||
exclude_files = set(opts.exclude_files)
|
|
||||||
exclude_dirs = set(os.path.abspath(_) for _ in opts.exclude_dirs)
|
|
||||||
|
|
||||||
# Convert target paths to absolute, and remove any that are excluded:
|
|
||||||
target_paths = set(os.path.abspath(_) for _ in targets if _ not in exclude_dirs)
|
|
||||||
logging.debug("Target paths: %s", target_paths)
|
|
||||||
|
|
||||||
# Build separate sets of target files and dirs. Later, we'll search target dirs and add
|
|
||||||
# matching target files to the files set.
|
|
||||||
target_files = set()
|
|
||||||
target_dirs = set()
|
|
||||||
for t in target_paths:
|
|
||||||
if os.path.isfile(t):
|
|
||||||
target_files.add(os.path.abspath(t))
|
|
||||||
elif os.path.isdir(t):
|
|
||||||
target_dirs.add(os.path.abspath(t))
|
|
||||||
logging.debug("Target files after separating: %s", target_files)
|
|
||||||
logging.debug("Target dirs after separating: %s", target_dirs)
|
|
||||||
|
|
||||||
# Remove excluded filenames:
|
|
||||||
target_files -= set(_ for _ in target_files if os.path.basename(_) in exclude_files)
|
|
||||||
|
|
||||||
# Remove files and dirs in excluded dirs:
|
|
||||||
target_files -= set(_ for _ in target_files for x in exclude_dirs if _.startswith(x))
|
|
||||||
target_dirs -= set(_ for _ in target_dirs for x in exclude_dirs if _.startswith(x))
|
|
||||||
|
|
||||||
# Compile regex for matching files to be formatted:
|
|
||||||
target_matcher = re.compile(opts.regex)
|
|
||||||
|
|
||||||
# Remove target files that don't match the regex:
|
|
||||||
logging.debug("Target files before matching regex: %s", target_files)
|
|
||||||
target_files = set(_ for _ in target_files if target_matcher.search(_))
|
|
||||||
logging.debug("Target files after matching regex: %s", target_files)
|
|
||||||
|
|
||||||
# Search target dirs for non-excluded files, and add them to `target_files`:
|
|
||||||
logging.debug("Searching target dirs: %s", target_dirs)
|
|
||||||
for path in target_dirs:
|
|
||||||
for root, dirs, files in os.walk(path):
|
|
||||||
# Prune excluded dirs
|
|
||||||
for x in exclude_dirs:
|
|
||||||
if x in (os.path.join(root, _) for _ in dirs):
|
|
||||||
dirs.remove(os.path.basename(x))
|
|
||||||
# Add non-excluded files
|
|
||||||
for f in files:
|
|
||||||
if target_matcher.search(f) and f not in exclude_files:
|
|
||||||
target_files.add(os.path.join(root, f))
|
|
||||||
|
|
||||||
if len(target_files) == 0:
|
|
||||||
logging.error("No target files found; exiting.")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Run clang-format on target files:
|
|
||||||
proc = subprocess.run(clang_format_cmd + sorted(target_files))
|
|
||||||
if proc.returncode != 0:
|
|
||||||
logging.error("Error: clang-format returned non-zero status: %s", proc.returncode)
|
|
||||||
return proc.returncode
|
|
||||||
else:
|
|
||||||
logging.info("Finished formatting target files.")
|
|
||||||
|
|
||||||
# If we've been asked to check for changes made by the formatter:
|
|
||||||
if opts.check:
|
|
||||||
logging.warning('Checking for changes made by the formatter...')
|
|
||||||
changed_files = check_git_diff()
|
|
||||||
if len(changed_files) == 0:
|
|
||||||
logging.warning("No files changed. Congratulations!")
|
|
||||||
else:
|
|
||||||
logging.warning("Found files with changes after formatting:")
|
|
||||||
exit_code = 1
|
|
||||||
for f in changed_files:
|
|
||||||
logging.warning(" %s", f)
|
|
||||||
|
|
||||||
return exit_code
|
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
sys.exit(main())
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
logging.info("Aborting")
|
|
||||||
sys.exit(1)
|
|
@ -1,12 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
: "${KALEIDOSCOPE_DIR:=$(pwd)}"
|
|
||||||
cd "${KALEIDOSCOPE_DIR}" || exit 1
|
|
||||||
|
|
||||||
: "${VERBOSE:=}"
|
|
||||||
|
|
||||||
"${KALEIDOSCOPE_DIR}/bin/format-code.py" \
|
|
||||||
--exclude-dir 'testing/googletest' \
|
|
||||||
--exclude-file 'generated-testcase.cpp' \
|
|
||||||
--verbose \
|
|
||||||
src plugins testing
|
|
@ -1,508 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# Copyright (c) 2022 Michael Richters <gedankenexperimenter@gmail.com>
|
|
||||||
|
|
||||||
# This is free and unencumbered software released into the public domain.
|
|
||||||
|
|
||||||
# Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
||||||
# distribute this software, either in source code form or as a compiled
|
|
||||||
# binary, for any purpose, commercial or non-commercial, and by any
|
|
||||||
# means.
|
|
||||||
|
|
||||||
# In jurisdictions that recognize copyright laws, the author or authors
|
|
||||||
# of this software dedicate any and all copyright interest in the
|
|
||||||
# software to the public domain. We make this dedication for the benefit
|
|
||||||
# of the public at large and to the detriment of our heirs and
|
|
||||||
# successors. We intend this dedication to be an overt act of
|
|
||||||
# relinquishment in perpetuity of all present and future rights to this
|
|
||||||
# software under copyright law.
|
|
||||||
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
||||||
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
||||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
||||||
# OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
# For more information, please refer to <http://unlicense.org/>
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
"""Run `include-what-you-use` on Kaleidoscope sources
|
|
||||||
|
|
||||||
This is a script for maintenance of the headers included in Kaleidoscope source
|
|
||||||
files. It is not currently possible to run this automatically on all
|
|
||||||
Kaleidoscope source files, because of the peculiarities therein. It uses
|
|
||||||
llvm/clang to determine which headers should be included in a given file, but
|
|
||||||
there's no avr-clang, so we're limited to using the virtual hardware device.
|
|
||||||
|
|
||||||
It takes any number of source files as its input, examines them and updates the
|
|
||||||
list of header `#include` directives.
|
|
||||||
|
|
||||||
It is safe to run on most Kaleidoscope source files, and a good idea to run it
|
|
||||||
on new ones (after staging them, so you can easily see what changes have been
|
|
||||||
made).
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Example invocation:
|
|
||||||
# $ git ls-files -m | grep '\.\(h\|cpp\)' | bin/iwyu.py
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import glob
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import shlex
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
sys.dont_write_bytecode = True
|
|
||||||
|
|
||||||
from common import cwd, setup_logging, split_on_newlines, split_on_nulls
|
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
def parse_args(args):
|
|
||||||
"""Parse command line parameters
|
|
||||||
|
|
||||||
Args:
|
|
||||||
args (list[str]): command line parameters as list of strings
|
|
||||||
(for example ``["--help"]``).
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
:obj:`argparse.Namespace`: command line parameters namespace
|
|
||||||
"""
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="""
|
|
||||||
Run `include-what-you-use` on source files given as command-line arguments and/or read
|
|
||||||
from standard input. When reading target filenames from standard input, they should be
|
|
||||||
either absolute or relative to the current directory, and each line of input (minus the
|
|
||||||
line-ending character(s) is treated as a filename.""")
|
|
||||||
parser.add_argument(
|
|
||||||
'-q',
|
|
||||||
'--quiet',
|
|
||||||
dest='loglevel',
|
|
||||||
action='store_const',
|
|
||||||
const=logging.ERROR,
|
|
||||||
default=logging.WARNING,
|
|
||||||
help="""
|
|
||||||
Suppress output except warnings and errors.""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-v',
|
|
||||||
'--verbose',
|
|
||||||
action='store_const',
|
|
||||||
dest='loglevel',
|
|
||||||
const=logging.INFO,
|
|
||||||
help="""
|
|
||||||
Output verbose debugging information.""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-d',
|
|
||||||
'--debug',
|
|
||||||
action='store_const',
|
|
||||||
dest='loglevel',
|
|
||||||
const=logging.DEBUG,
|
|
||||||
help="""
|
|
||||||
Save output from `include-what-you-use` for processed files beside the originals, with
|
|
||||||
a '.iwyu' suffix, for debugging purposes.""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-r',
|
|
||||||
'--regex',
|
|
||||||
action='store',
|
|
||||||
dest='regex',
|
|
||||||
default=r'\.(h|cpp)$',
|
|
||||||
help="""
|
|
||||||
A regular expression for matching filenames Only the basename of the file is
|
|
||||||
matched, and the regex is only used when searching a directory for files to process,
|
|
||||||
not on target filenames specified in arguments or read from standard input.""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-i',
|
|
||||||
'--ignores_file',
|
|
||||||
action='store',
|
|
||||||
dest='ignores_file',
|
|
||||||
default='.iwyu_ignore',
|
|
||||||
metavar='<ignores_file>',
|
|
||||||
help="""
|
|
||||||
The name of a file (relative to KALEIDOSCOPE_DIR) that contains a list of glob patterns
|
|
||||||
that will be ignored when a target directory is searched for filenames that match
|
|
||||||
<regex>.""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-I',
|
|
||||||
'--include',
|
|
||||||
action='append',
|
|
||||||
dest='include_dirs',
|
|
||||||
metavar='<dir>',
|
|
||||||
help="""
|
|
||||||
Add <dir> to the list of directories that will be searched for header files.""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-z',
|
|
||||||
'-0',
|
|
||||||
action='store_const',
|
|
||||||
dest='input_splitter',
|
|
||||||
const=split_on_nulls,
|
|
||||||
default=split_on_newlines,
|
|
||||||
help="""
|
|
||||||
When reading target filenames from standard input, break on NULL characters instead
|
|
||||||
of newlines.""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--update_comments',
|
|
||||||
action='store_true',
|
|
||||||
help="""
|
|
||||||
Call `include-what-you-use` with `--update_comments` to always rewrite its 'for'
|
|
||||||
comments regarding which symbols are used.""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'targets',
|
|
||||||
nargs='*',
|
|
||||||
metavar="<target>",
|
|
||||||
help="""
|
|
||||||
A list of target files and/or directories to search for source files to format. Any
|
|
||||||
target file will be processed, regardless of the filename. Any target directory will
|
|
||||||
be recursively searched for files matching the regular expression given by --regex.
|
|
||||||
Filenames and directories beginning with a '.' will always be excluded from the search,
|
|
||||||
but can still be processed if specified as a command-line target.""",
|
|
||||||
)
|
|
||||||
return parser.parse_args(args)
|
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
def main():
|
|
||||||
"""Main entry point function."""
|
|
||||||
# Parse command-line arguments:
|
|
||||||
opts = parse_args(sys.argv[1:])
|
|
||||||
# Set up logging system:
|
|
||||||
setup_logging(opts.loglevel)
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
# Find include-what-you-use:
|
|
||||||
iwyu = shutil.which('include-what-you-use')
|
|
||||||
logging.debug("Found `include-what-you-use` executable: %s", iwyu)
|
|
||||||
iwyu_opts = [
|
|
||||||
'--no_fwd_decls', # No forward declarations
|
|
||||||
'--max_line_length=100',
|
|
||||||
]
|
|
||||||
if opts.update_comments:
|
|
||||||
iwyu_opts.append('--update_comments')
|
|
||||||
# Prepend '-Xiwyu' to each `include-what-you-use` option:
|
|
||||||
iwyu_opts = [_ for opt in iwyu_opts for _ in ('-Xiwyu', opt)]
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
# Find fix_includes:
|
|
||||||
fix_includes = shutil.which('fix_includes.py')
|
|
||||||
logging.debug("Found `fix_includes` executable: %s", fix_includes)
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
# Find clang (first checking environment variable):
|
|
||||||
clang = os.getenv('CLANG_COMPILER')
|
|
||||||
if clang is None:
|
|
||||||
clang = shutil.which('clang')
|
|
||||||
logging.debug("Found `clang` executable: %s", clang)
|
|
||||||
|
|
||||||
# Get system include dir from `clang`:
|
|
||||||
clang_cmd = [clang, '-print-resource-dir']
|
|
||||||
logging.debug("Running command: `%s`", shlex.join(clang_cmd))
|
|
||||||
result = subprocess.run(clang_cmd, capture_output=True, check=True)
|
|
||||||
clang_resource_dir = result.stdout.decode('utf-8').rstrip()
|
|
||||||
system_include_dir = os.path.join(clang_resource_dir, 'include')
|
|
||||||
logging.debug("Using system include dir: %s", system_include_dir)
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
# Get $KALEIDOSCOPE_DIR from enironment, falling back on `pwd`:
|
|
||||||
kaleidoscope_dir = os.getenv('KALEIDOSCOPE_DIR')
|
|
||||||
if kaleidoscope_dir is None:
|
|
||||||
kaleidoscope_dir = os.getcwd()
|
|
||||||
logging.debug("Using Kaleidoscope dir: %s", kaleidoscope_dir)
|
|
||||||
kaleidoscope_src_dir = os.path.join(kaleidoscope_dir, 'src')
|
|
||||||
|
|
||||||
# Define locations of other dirs to find Arduino libraries:
|
|
||||||
virtual_hardware_dir = os.path.join(
|
|
||||||
kaleidoscope_dir, '.arduino', 'user', 'hardware', 'keyboardio', 'virtual')
|
|
||||||
logging.debug("Using virtual hardware dir: %s", virtual_hardware_dir)
|
|
||||||
|
|
||||||
virtual_arduino_core_dir = os.path.join(virtual_hardware_dir, 'cores', 'arduino')
|
|
||||||
logging.debug("Using virtual arduino core: %s", virtual_arduino_core_dir)
|
|
||||||
|
|
||||||
virtual_model01_dir = os.path.join(virtual_hardware_dir, 'variants', 'model01')
|
|
||||||
logging.debug("Using virtual Model01 dir: %s", virtual_model01_dir)
|
|
||||||
|
|
||||||
virtual_keyboardiohid_dir = os.path.join(
|
|
||||||
virtual_hardware_dir, 'libraries', 'KeyboardioHID', 'src')
|
|
||||||
logging.debug("Using virtual KeyboardioHID dir: %s", virtual_keyboardiohid_dir)
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
# Create the long list of options passed to `clang` via `include-what-you-use`.
|
|
||||||
# First, we tell it we're using C++:
|
|
||||||
clang_opts = ['-x', 'c++']
|
|
||||||
# General compiler options:
|
|
||||||
clang_opts += [
|
|
||||||
'-c',
|
|
||||||
'-g',
|
|
||||||
'-Wall',
|
|
||||||
'-Wextra',
|
|
||||||
'-std=gnu++14', # Not `c++14`, because we're using clang, not gcc
|
|
||||||
'-ffunction-sections',
|
|
||||||
'-fdata-sections',
|
|
||||||
'-fno-threadsafe-statics',
|
|
||||||
'-MMD',
|
|
||||||
'-Woverloaded-virtual',
|
|
||||||
'-Wno-unused-parameter',
|
|
||||||
'-Wno-unused-variable',
|
|
||||||
'-Wno-ignored-qualifiers',
|
|
||||||
'-Wno-type-limits',
|
|
||||||
'-Wno-pragma-once-outside-header',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Variables we define to do a Kaleidoscope build:
|
|
||||||
defines = [
|
|
||||||
'KALEIDOSCOPE_VIRTUAL_BUILD=1',
|
|
||||||
'KEYBOARDIOHID_BUILD_WITHOUT_HID=1',
|
|
||||||
'USBCON=dummy',
|
|
||||||
'ARDUINO_ARCH_AVR=1',
|
|
||||||
'ARDUINO=10607',
|
|
||||||
'ARDUINO_AVR_MODEL01',
|
|
||||||
'ARDUINO_ARCH_VIRTUAL',
|
|
||||||
'USB_VID=0x1209',
|
|
||||||
'USB_PID=0x2301',
|
|
||||||
'USB_MANUFACTURER="Keyboardio"',
|
|
||||||
'USB_PRODUCT="Model 01"',
|
|
||||||
'KALEIDOSCOPE_HARDWARE_H="Kaleidoscope-Hardware-Keyboardio-Model01.h"',
|
|
||||||
'TWI_BUFFER_LENGTH=32',
|
|
||||||
]
|
|
||||||
clang_opts += ['-D' + _ for _ in defines]
|
|
||||||
|
|
||||||
# Directories to search for libraries to include:
|
|
||||||
includes = [
|
|
||||||
system_include_dir,
|
|
||||||
kaleidoscope_src_dir,
|
|
||||||
virtual_arduino_core_dir,
|
|
||||||
virtual_model01_dir,
|
|
||||||
virtual_keyboardiohid_dir,
|
|
||||||
]
|
|
||||||
# Include plugin source dirs for plugins that depend on other plugins:
|
|
||||||
includes += glob.glob(os.path.join(kaleidoscope_dir, 'plugins', '*', 'src'))
|
|
||||||
# Include dirs specified on the command line, if any:
|
|
||||||
if opts.include_dirs:
|
|
||||||
includes += [os.path.abspath(_) for _ in opts.include_dirs]
|
|
||||||
clang_opts += ['-I' + _ for _ in includes]
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
# Define the `include-what-you-use` command (sans target files)
|
|
||||||
iwyu_cmd = [iwyu] + iwyu_opts + clang_opts
|
|
||||||
logging.debug("Using IWYU command: %s", ' \\\n\t'.join(iwyu_cmd))
|
|
||||||
|
|
||||||
fix_includes_cmd = [
|
|
||||||
fix_includes,
|
|
||||||
'--nosafe_headers',
|
|
||||||
'--reorder',
|
|
||||||
'--separate_project_includes=' + kaleidoscope_src_dir, # Does this help?
|
|
||||||
]
|
|
||||||
if opts.update_comments:
|
|
||||||
fix_includes_cmd.append('--update_comments')
|
|
||||||
logging.debug("Using `fix_includes` command: %s", ' \\\n\t'.join(fix_includes_cmd))
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
targets = opts.targets
|
|
||||||
# If stdin is a pipe, read pathname targets, one per line. This allows us to connect the
|
|
||||||
# output of `find` to our input conveniently:
|
|
||||||
if not sys.stdin.isatty():
|
|
||||||
logging.debug("Reading targets from STDIN...")
|
|
||||||
targets += opts.input_splitter(sys.stdin.read())
|
|
||||||
for t in targets:
|
|
||||||
logging.debug(" Target path: %s", t)
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
iwyu_ignores_file = os.path.join(kaleidoscope_dir, opts.ignores_file)
|
|
||||||
ignores = build_ignores_list(iwyu_ignores_file)
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
regex = re.compile(opts.regex)
|
|
||||||
# Process source files first, then header files, because a source file might have been
|
|
||||||
# relying on a header included by its associated header, but which that header does not
|
|
||||||
# need on its own. In this case, if we process the header first, IWYU won't be able to
|
|
||||||
# parse the source file, and we'll get an error, but if we do them in the other order,
|
|
||||||
# the source file will get all the includes it needs before the header removes any of them.
|
|
||||||
source_files = []
|
|
||||||
header_files = []
|
|
||||||
for target_file in (_ for t in targets for _ in build_target_list(t, regex, ignores)):
|
|
||||||
if target_file.endswith('.cpp') or target_file.endswith('.ino'):
|
|
||||||
source_files.append(target_file)
|
|
||||||
else:
|
|
||||||
header_files.append(target_file)
|
|
||||||
|
|
||||||
# If there's an error processing any file, return an error code.
|
|
||||||
exit_code = 0
|
|
||||||
for target_file in source_files + header_files:
|
|
||||||
# Run IWYU and fix_headers:
|
|
||||||
if not run_iwyu(os.path.relpath(target_file), iwyu_cmd, fix_includes_cmd):
|
|
||||||
exit_code = 1
|
|
||||||
|
|
||||||
return exit_code
|
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
def build_target_list(target, regex, ignores):
|
|
||||||
"""Build the list of target files, starting from a file or directory.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
target (str): The name of the file or directory to search
|
|
||||||
regex (Pattern): A compiled regular expression to match target file names
|
|
||||||
ignores (list): A list of (absolute) file names to be excluded from the target list
|
|
||||||
Returns:
|
|
||||||
targets (list): A list of (absolute) file names to be processed
|
|
||||||
|
|
||||||
Given a filename or directory (`path`), returns a list of target filenames to run IWYU
|
|
||||||
on. Target filenames will all be absolute, but `path` can be relative to the current
|
|
||||||
directory. All target filenames will match `regex`, and any items in `ignores` will be
|
|
||||||
excluded. If an element of the `ignores` list is a directory, all files and directories
|
|
||||||
beneath it will be excluded from the search.
|
|
||||||
"""
|
|
||||||
logging.debug("Searching target: %s", target)
|
|
||||||
|
|
||||||
# Convert all paths to absolute.
|
|
||||||
root = os.path.abspath(target)
|
|
||||||
|
|
||||||
# If `path` is a file, and it matches `regex`, add it to the target list.
|
|
||||||
if os.path.isfile(target) and regex.search(target) and root not in ignores:
|
|
||||||
return [root]
|
|
||||||
|
|
||||||
# If the specified path is not valid, just return an empty list.
|
|
||||||
if not os.path.isdir(target):
|
|
||||||
logging.error("Error: File not found: %s", target)
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Start with an empty list.
|
|
||||||
targets = []
|
|
||||||
|
|
||||||
# The specified path is a directory, so we search recursively for files
|
|
||||||
# contained therein that match the specified regular expression.
|
|
||||||
for path, dirs, files in os.walk(root):
|
|
||||||
logging.debug("Searching dir: %s", os.path.relpath(path))
|
|
||||||
# First, ignore all dotfiles (and directories).
|
|
||||||
for x in (os.path.basename(_)
|
|
||||||
for _ in ignores + glob.glob(os.path.join(path, '.*'))
|
|
||||||
if os.path.dirname(_) == path):
|
|
||||||
if x in dirs:
|
|
||||||
logging.info("Skipping ignored dir: %s", os.path.join(path, x))
|
|
||||||
dirs.remove(x)
|
|
||||||
if x in files:
|
|
||||||
logging.info("Skipping ignored file: %s", os.path.join(path, x))
|
|
||||||
files.remove(x)
|
|
||||||
|
|
||||||
logging.debug("Files found: %s", ', '.join(files))
|
|
||||||
# Add all matching files to the list of source files to be formatted.
|
|
||||||
for f in (_ for _ in files if regex.search(_)):
|
|
||||||
t = os.path.join(path, f)
|
|
||||||
logging.debug("Source file found: %s", t)
|
|
||||||
targets.append(t)
|
|
||||||
|
|
||||||
return targets
|
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
def build_ignores_list(ignores_file_path):
|
|
||||||
"""Build a list of files and dirs to exclude from processing by IWYU
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
ignores_file_path (str): The name of the file to read ignores globs from
|
|
||||||
Returns:
|
|
||||||
ignores_list (list): A list of (absolute) file names to exclude from processing
|
|
||||||
|
|
||||||
Reads `ignores_file` and expands each line as a glob, returning a list of all the target
|
|
||||||
file names to be excluded from processing. Each path in the file is treated as a relative
|
|
||||||
pathname (unless it is already absolute), relative to the directory in which `ignores_file`
|
|
||||||
is located.
|
|
||||||
"""
|
|
||||||
logging.debug("Searching for ignores file: %s", ignores_file_path)
|
|
||||||
# If the ignores file doesn't exist, return an empty list:
|
|
||||||
if not os.path.isfile(ignores_file_path):
|
|
||||||
logging.debug("Ignores file not found")
|
|
||||||
return []
|
|
||||||
|
|
||||||
ignores_list = []
|
|
||||||
with open(ignores_file_path) as f:
|
|
||||||
for line in f.read().splitlines():
|
|
||||||
# Lines starting with `#` are treated as comments.
|
|
||||||
if line.startswith('#'):
|
|
||||||
continue
|
|
||||||
logging.debug("Ignoring files like: %s", line)
|
|
||||||
ignores_list += glob.glob(line, recursive=True)
|
|
||||||
|
|
||||||
# Get the dir of the ignores file so we can construct absolute pathnames.
|
|
||||||
ignores_file_dir = os.path.dirname(ignores_file_path)
|
|
||||||
with cwd(ignores_file_dir):
|
|
||||||
ignores_list[:] = [os.path.abspath(_) for _ in ignores_list]
|
|
||||||
|
|
||||||
logging.debug("Ignores list:\n\t%s", "\n\t".join(ignores_list))
|
|
||||||
return ignores_list
|
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
def run_iwyu(source_file, iwyu_cmd, fix_includes_cmd):
|
|
||||||
"""Run IWYU and fix_includes on a single source file
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
source_file (str): The name of a file to run IWYU on
|
|
||||||
iwyu_cmd (list): The command name and options list for `include-what-you-use`
|
|
||||||
fix_includes_cmd (list): The command name and options list for `fix_headers.py`
|
|
||||||
Returns:
|
|
||||||
True on success, False if either IWYU or fix_headers returns an error code
|
|
||||||
|
|
||||||
Run `include-what-you-use` on <source_file>, an update that file's header includes by
|
|
||||||
sending the output to `fix_includes.py`. If either command returns an error code, return
|
|
||||||
`False`, otherwise return `True`.
|
|
||||||
"""
|
|
||||||
logging.info("Fixing headers in file: %s", source_file)
|
|
||||||
|
|
||||||
# Run IWYU on <source_file>
|
|
||||||
iwyu_proc = subprocess.run(iwyu_cmd + [source_file], capture_output=True, check=False)
|
|
||||||
|
|
||||||
# If IWYU returns an error, report on it:
|
|
||||||
if iwyu_proc.returncode != 0:
|
|
||||||
logging.error("Error: failed to parse file: %s", source_file)
|
|
||||||
logging.debug("IWYU returned: %s", iwyu_proc.returncode)
|
|
||||||
logging.debug("STDOUT:\n%s", iwyu_proc.stdout.decode('utf-8'))
|
|
||||||
logging.debug("STDERR:\n%s", iwyu_proc.stderr.decode('utf-8'))
|
|
||||||
# In addition to reporting the error, save the output for analysis:
|
|
||||||
with open(source_file + '.iwyu', 'wb') as f:
|
|
||||||
f.write(iwyu_proc.stderr)
|
|
||||||
# Don't run fix_includes if there was an error (or if we've got an old version of IWYU
|
|
||||||
# that returns bogus exit codes):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# IWYU reports on the associated header of *.cpp files, but if we want to skip processing
|
|
||||||
# that header, we need to use only the part of the output for the *.cpp file. Fortunately,
|
|
||||||
# the header is listed first, so we only need to search for the start of the target file's
|
|
||||||
# section of the output.
|
|
||||||
n = iwyu_proc.stderr.find(f"\n{source_file} should".encode('utf-8'))
|
|
||||||
iwyu_stderr = iwyu_proc.stderr[n:]
|
|
||||||
|
|
||||||
# Run fix_includes.py, using the output (stderr) of IWYU:
|
|
||||||
fix_includes_proc = subprocess.run(
|
|
||||||
fix_includes_cmd, input=iwyu_stderr, capture_output=True, check=False)
|
|
||||||
|
|
||||||
# Report any errors returned by fix_includes.py:
|
|
||||||
if fix_includes_proc.returncode != 0:
|
|
||||||
logging.error("Error: failed to fix includes for file: %s", source_file)
|
|
||||||
logging.debug("fix_includes.py returned: %s", fix_includes_proc.returncode)
|
|
||||||
logging.debug("STDOUT:\n%s", fix_includes_proc.stdout.decode('utf-8'))
|
|
||||||
logging.debug("STDERR:\n%s", fix_includes_proc.stderr.decode('utf-8'))
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Return true on success, false otherwise:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
sys.exit(main())
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
logging.info("Aborting")
|
|
||||||
sys.exit(1)
|
|
@ -0,0 +1,738 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# kaleidoscope-builder - Kaleidoscope helper tool
|
||||||
|
# Copyright (C) 2017-2018 Keyboard.io, Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation, version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
######
|
||||||
|
###### Build and output configuration
|
||||||
|
######
|
||||||
|
|
||||||
|
absolute_filename() {
|
||||||
|
echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
build_version () {
|
||||||
|
: "${LIB_PROPERTIES_PATH:="../.."}"
|
||||||
|
GIT_VERSION="$(cd "${SKETCH_DIR}"; if [ -d .git ]; then echo -n '-g' && git describe --abbrev=4 --dirty --always; fi)"
|
||||||
|
LIB_VERSION="$(cd "${SKETCH_DIR}"; (grep version= "${LIB_PROPERTIES_PATH}/library.properties" 2>/dev/null || echo version=0.0.0) | cut -d= -f2)${GIT_VERSION}"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_paths() {
|
||||||
|
# We need that echo because we\re piping to cksum
|
||||||
|
# shellcheck disable=SC2005
|
||||||
|
SKETCH_IDENTIFIER="$(echo "${SKETCH_FILE_PATH}" | cksum | cut -d ' ' -f 1)-${SKETCH_FILE_NAME}"
|
||||||
|
: "${KALEIDOSCOPE_TEMP_PATH:=${TMPDIR:-/tmp}/kaleidoscope-${USER}}"
|
||||||
|
|
||||||
|
: "${KALEIDOSCOPE_BUILD_PATH:=${KALEIDOSCOPE_TEMP_PATH}/sketch}"
|
||||||
|
: "${KALEIDOSCOPE_OUTPUT_PATH:=${KALEIDOSCOPE_TEMP_PATH}/sketch}"
|
||||||
|
|
||||||
|
: "${SKETCH_OUTPUT_DIR:=${SKETCH_IDENTIFIER}/output}"
|
||||||
|
: "${SKETCH_BUILD_DIR:=${SKETCH_IDENTIFIER}/build}"
|
||||||
|
|
||||||
|
: "${BUILD_PATH:=${KALEIDOSCOPE_BUILD_PATH}/${SKETCH_BUILD_DIR}}"
|
||||||
|
: "${OUTPUT_PATH:=${KALEIDOSCOPE_OUTPUT_PATH}/${SKETCH_OUTPUT_DIR}}"
|
||||||
|
|
||||||
|
: "${CCACHE_WRAPPER_PATH:=${KALEIDOSCOPE_TEMP_PATH}/ccache/bin}"
|
||||||
|
: "${CORE_CACHE_PATH:=${KALEIDOSCOPE_TEMP_PATH}/arduino-cores}"
|
||||||
|
|
||||||
|
mkdir -p "$CORE_CACHE_PATH"
|
||||||
|
mkdir -p "$BUILD_PATH"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_filenames () {
|
||||||
|
: "${OUTPUT_FILE_PREFIX:=${SKETCH_BASE_NAME}-${LIB_VERSION}}"
|
||||||
|
: "${HEX_FILE_PATH:=${OUTPUT_PATH}/${OUTPUT_FILE_PREFIX}.hex}"
|
||||||
|
: "${HEX_FILE_WITH_BOOTLOADER_PATH:=${OUTPUT_PATH}/${OUTPUT_FILE_PREFIX}-with-bootloader.hex}"
|
||||||
|
: "${ELF_FILE_PATH:=${OUTPUT_PATH}/${OUTPUT_FILE_PREFIX}.elf}"
|
||||||
|
: "${LIB_FILE_PATH:=${OUTPUT_PATH}/${OUTPUT_FILE_PREFIX}.a}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enable_ccache () {
|
||||||
|
if [ -z "${CCACHE_NOT_SUPPORTED}" ] && [ "$(command -v ccache)" ]; then
|
||||||
|
if ! [ -d "$CCACHE_WRAPPER_PATH" ]; then
|
||||||
|
mkdir -p "$CCACHE_WRAPPER_PATH"
|
||||||
|
|
||||||
|
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}${C_COMPILER_BASENAME}" ]; then
|
||||||
|
ln -s "$(command -v ccache)" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}${C_COMPILER_BASENAME}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}${CXX_COMPILER_BASENAME}" ]; then
|
||||||
|
ln -s "$(command -v ccache)" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}${CXX_COMPILER_BASENAME}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}nm" ]; then
|
||||||
|
ln -s "${AVR_NM}" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}nm"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}objcopy" ]; then
|
||||||
|
ln -s "${AVR_OBJCOPY}" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}objcopy"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}ar" ]; then
|
||||||
|
ln -s "${AVR_AR}" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}ar"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}size" ]; then
|
||||||
|
ln -s "${AVR_SIZE}" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}size"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
export CCACHE_PATH=${COMPILER_PATH}/
|
||||||
|
CCACHE_ENABLE="-prefs compiler.path=${CCACHE_WRAPPER_PATH}/"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
firmware_size () {
|
||||||
|
if [ "${ARCH}" = "virtual" ]; then
|
||||||
|
echo "[Size not computed for virtual build]"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
: "${MAX_PROG_SIZE:=$(get_arduino_pref 'upload.maximum_size')}"
|
||||||
|
|
||||||
|
## This is a terrible hack, please don't hurt me. - algernon
|
||||||
|
|
||||||
|
set +e
|
||||||
|
raw_output=$("$@" 2> /dev/null)
|
||||||
|
rc=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ $rc -eq 0 ]; then
|
||||||
|
output="$(echo "${raw_output}"| grep "\\(Program\\|Data\\):" | sed -e 's,^, - ,' && echo)"
|
||||||
|
|
||||||
|
PROGSIZE="$(echo "${output}" | grep "Program:" | cut -d: -f2 | awk '{print $1}')"
|
||||||
|
|
||||||
|
PERCENT="$(echo "${PROGSIZE}" "${MAX_PROG_SIZE}" | awk "{ printf \"%02.01f\", \$1 / \$2 * 100 }")"
|
||||||
|
|
||||||
|
# we want the sed there, doing with shell builtins would be worse.
|
||||||
|
# shellcheck disable=SC2001 disable=SC1117
|
||||||
|
echo "${output}" | sed -e "s/\(Program:.*\)(\([0-9\.]*%\) Full)/\1(${PERCENT}% Full)/"
|
||||||
|
else
|
||||||
|
echo "Unable to determine image size."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
find_sketch () {
|
||||||
|
if [ -z "${SKETCH}" ]; then
|
||||||
|
echo "SKETCH needs to be set before including this file!" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SKETCH_DIR="${SKETCH}"
|
||||||
|
SKETCH_BASE_NAME=$(basename "$SKETCH")
|
||||||
|
SKETCH_FILE_NAME="${SKETCH_BASE_NAME}.ino"
|
||||||
|
|
||||||
|
for path in "${SKETCH_DIR}" \
|
||||||
|
"src" \
|
||||||
|
"."; do
|
||||||
|
if [ -f "${path}/${SKETCH_FILE_NAME}" ]; then
|
||||||
|
SKETCH_DIR="${path}"
|
||||||
|
SKETCH_FILE_PATH=$(absolute_filename "${SKETCH_DIR}/${SKETCH_FILE_NAME}")
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "I couldn't find your sketch (.ino file)" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
prompt_before_flashing () {
|
||||||
|
flashing_instructions=$(get_arduino_pref 'build.flashing_instructions')
|
||||||
|
|
||||||
|
if [ "x${flashing_instructions}x" = "xx" ]; then
|
||||||
|
flashing_instructions="If your keyboard needs you to do something to put it in flashing mode, do that now."
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%b\n\n' "${flashing_instructions}"
|
||||||
|
echo ""
|
||||||
|
echo "When you're ready to proceed, press 'Enter'."
|
||||||
|
|
||||||
|
# We do not want to permit line continuations here. We just want a newline.
|
||||||
|
# shellcheck disable=SC2162
|
||||||
|
read
|
||||||
|
}
|
||||||
|
|
||||||
|
flash () {
|
||||||
|
compile "$@"
|
||||||
|
|
||||||
|
# Check to see if we can see a keyboard bootloader port.
|
||||||
|
# If we -can-, then we should skip over the "reset to bootloader" thing
|
||||||
|
find_bootloader_ports
|
||||||
|
if [ -z "${DEVICE_PORT_BOOTLOADER}" ]; then
|
||||||
|
prompt_before_flashing
|
||||||
|
|
||||||
|
# This is defined in the (optional) user config.
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
${preFlash_HOOKS}
|
||||||
|
|
||||||
|
# If we're -not- doing a manual reset, then try to do it automatically
|
||||||
|
if [ -z "${MANUAL_RESET}" ]; then
|
||||||
|
reset_device
|
||||||
|
sleep 2
|
||||||
|
find_bootloader_ports
|
||||||
|
# Otherwise, poll for a bootloader port.
|
||||||
|
else
|
||||||
|
wait_for_bootloader_port
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_bootloader_port_and_flash
|
||||||
|
|
||||||
|
# This is defined in the (optional) user config.
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
${postFlash_HOOKS}
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_bootloader_port() {
|
||||||
|
declare -i tries
|
||||||
|
tries=15
|
||||||
|
|
||||||
|
while [ "$tries" -gt 0 ] && [ -z "${DEVICE_PORT_BOOTLOADER}" ]; do
|
||||||
|
sleep 1
|
||||||
|
printf "."
|
||||||
|
find_bootloader_ports
|
||||||
|
# the variable annotations do appear to be necessary
|
||||||
|
# shellcheck disable=SC2004
|
||||||
|
tries=$(($tries-1))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$tries" -gt 0 ]; then
|
||||||
|
echo "Found."
|
||||||
|
else
|
||||||
|
echo "Timed out."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_bootloader_port () {
|
||||||
|
if [ -z "${DEVICE_PORT_BOOTLOADER}" ]; then
|
||||||
|
echo "Unable to detect a keyboard in bootloader mode."
|
||||||
|
echo "You may need to hold a key or hit a reset button."
|
||||||
|
echo "Please check your keyboard's documentation"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
check_bootloader_port_and_flash () {
|
||||||
|
|
||||||
|
if ! check_bootloader_port; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Flashing your keyboard:"
|
||||||
|
|
||||||
|
# If the flash fails, try a second time
|
||||||
|
if ! flash_over_usb; then
|
||||||
|
sleep 2
|
||||||
|
if ! flash_over_usb; then
|
||||||
|
if [ "${ARDUINO_VERBOSE}" != "-verbose" ]; then
|
||||||
|
echo "Something went wrong."
|
||||||
|
echo "You might want to try flashing again with the VERBOSE environment variable set"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "Keyboard flashed successfully!"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
flash_over_usb () {
|
||||||
|
|
||||||
|
FLASH_CMD=$(${AVRDUDE} \
|
||||||
|
-C "${AVRDUDE_CONF}" \
|
||||||
|
-p"${MCU}" \
|
||||||
|
-cavr109 \
|
||||||
|
-D \
|
||||||
|
-P "${DEVICE_PORT_BOOTLOADER}" \
|
||||||
|
-b57600 \
|
||||||
|
"-Uflash:w:${HEX_FILE_PATH}:i")
|
||||||
|
|
||||||
|
if [ "${ARDUINO_VERBOSE}" != "-verbose" ]; then
|
||||||
|
${FLASH_CMD} 2>&1 |grep -v ^avrdude | grep -v '^$' |grep -v '^ ' | grep -vi programmer
|
||||||
|
return "${PIPESTATUS[0]}"
|
||||||
|
else
|
||||||
|
${FLASH_CMD}
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
flash_from_bootloader() {
|
||||||
|
compile "$@"
|
||||||
|
prompt_before_flashing
|
||||||
|
find_bootloader_ports
|
||||||
|
check_bootloader_port_and_flash
|
||||||
|
}
|
||||||
|
|
||||||
|
program() {
|
||||||
|
compile "$@"
|
||||||
|
prompt_before_flashing
|
||||||
|
flash_with_programmer
|
||||||
|
}
|
||||||
|
|
||||||
|
flash_with_programmer() {
|
||||||
|
${AVRDUDE} -v \
|
||||||
|
-C "${AVRDUDE_CONF}" \
|
||||||
|
-p"${MCU}" \
|
||||||
|
-cusbtiny \
|
||||||
|
-D \
|
||||||
|
-B 1 \
|
||||||
|
"-Uflash:w:${HEX_FILE_PATH}:i"
|
||||||
|
}
|
||||||
|
|
||||||
|
find_bootloader_path() {
|
||||||
|
BOOTLOADER_FILE=$( get_arduino_pref 'bootloader.file' )
|
||||||
|
: "${BOOTLOADER_FILE:=caterina/Caterina.hex}"
|
||||||
|
: "${BOOTLOADER_PATH:=${BOARD_HARDWARE_PATH}/keyboardio/avr/bootloaders/${BOOTLOADER_FILE}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
hex_with_bootloader () {
|
||||||
|
compile
|
||||||
|
|
||||||
|
find_bootloader_path
|
||||||
|
|
||||||
|
awk '/^:00000001FF/ == 0' "${HEX_FILE_PATH}" > "${HEX_FILE_WITH_BOOTLOADER_PATH}"
|
||||||
|
echo "Using ${BOOTLOADER_PATH}"
|
||||||
|
${MD5} "${BOOTLOADER_PATH}"
|
||||||
|
cat "${BOOTLOADER_PATH}" >> "${HEX_FILE_WITH_BOOTLOADER_PATH}"
|
||||||
|
ln -sf -- "${OUTPUT_FILE_PREFIX}-with-bootloader.hex" "${OUTPUT_PATH}/${SKETCH_BASE_NAME}-latest-with-bootloader.hex"
|
||||||
|
cat <<- EOF
|
||||||
|
|
||||||
|
Combined firmware and bootloader are now at ${HEX_FILE_WITH_BOOTLOADER_PATH}
|
||||||
|
Make sure you have the bootloader version you expect.
|
||||||
|
|
||||||
|
And TEST THIS ON REAL HARDWARE BEFORE YOU GIVE IT TO ANYONE
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
build () {
|
||||||
|
compile "$@"
|
||||||
|
size "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
prepare_ccache () {
|
||||||
|
build_paths
|
||||||
|
enable_ccache
|
||||||
|
}
|
||||||
|
compile () {
|
||||||
|
find_sketch
|
||||||
|
build_version
|
||||||
|
build_paths
|
||||||
|
build_filenames
|
||||||
|
# If the hex file is older than the sketch file, or the hex file does not exist
|
||||||
|
# then rebuild. This is not as correct as letting make check our dependencies
|
||||||
|
# But it's less broken for most user use cases
|
||||||
|
# TODO(anyone): Make this suck less
|
||||||
|
if [ "${HEX_FILE_PATH}" -ot "${SKETCH_FILE_PATH}" ]; then
|
||||||
|
do_compile "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
do_compile () {
|
||||||
|
prepare_ccache
|
||||||
|
|
||||||
|
install -d "${OUTPUT_PATH}"
|
||||||
|
|
||||||
|
echo "Building ${SKETCH_FILE_PATH}"
|
||||||
|
|
||||||
|
# This is defined in the (optional) user config.
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
${compile_HOOKS}
|
||||||
|
|
||||||
|
if [ -d "${ARDUINO_LOCAL_LIB_PATH}/libraries" ]; then
|
||||||
|
# shellcheck disable=SC2089
|
||||||
|
# We want literal backslashes here, not arrays.
|
||||||
|
local_LIBS="-libraries \"${ARDUINO_LOCAL_LIB_PATH}/libraries\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
ARDUINO_PACKAGES=""
|
||||||
|
if [ -d "${ARDUINO_PACKAGE_PATH}" ]; then
|
||||||
|
# shellcheck disable=SC2089
|
||||||
|
# We want literal backslashes here, not arrays.
|
||||||
|
ARDUINO_PACKAGES="-hardware \"${ARDUINO_PACKAGE_PATH}\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
SAVED_BOARD="${BOARD}"
|
||||||
|
SAVED_FQBN="${FQBN}"
|
||||||
|
if [ -e "${SKETCH_DIR}/.kaleidoscope-builder.conf" ]; then
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
BOARD="$(. "${SKETCH_DIR}"/.kaleidoscope-builder.conf && echo "${BOARD}")"
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
FQBN="$(. "${SKETCH_DIR}"/.kaleidoscope-builder.conf && echo "${FQBN}")"
|
||||||
|
if [ -n "${BOARD}" ]; then
|
||||||
|
if [ -z "${ARCH}" ]; then
|
||||||
|
FQBN="keyboardio:avr:${BOARD}"
|
||||||
|
else
|
||||||
|
FQBN="keyboardio:${ARCH}:${BOARD}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
_CMD_CXX="${CXX:-${COMPILER_PREFIX}${CXX_COMPILER_BASENAME}${COMPILER_SUFFIX}}"
|
||||||
|
_CMD_CC="${CC:-${COMPILER_PREFIX}${C_COMPILER_BASENAME}${COMPILER_SUFFIX}}"
|
||||||
|
_CMD_AR="${AR:-${COMPILER_PREFIX}${AR_BASENAME}${COMPILER_SUFFIX}}"
|
||||||
|
|
||||||
|
# SC2091: We do not care if quotes or backslashes are not respected.
|
||||||
|
# SC2086: We want word splitting.
|
||||||
|
# shellcheck disable=SC2086,SC2090
|
||||||
|
"${ARDUINO_BUILDER}" \
|
||||||
|
-compile \
|
||||||
|
${ARDUINO_PACKAGES} \
|
||||||
|
-hardware "${ARDUINO_PATH}/hardware" \
|
||||||
|
-hardware "${BOARD_HARDWARE_PATH}" \
|
||||||
|
${ARDUINO_TOOLS_FLAG:+"${ARDUINO_TOOLS_FLAG}"} ${ARDUINO_TOOLS_PARAM:+"${ARDUINO_TOOLS_PARAM}"} \
|
||||||
|
-tools "${ARDUINO_BUILDER_TOOLS_PATH}" \
|
||||||
|
-fqbn "${FQBN}" \
|
||||||
|
-libraries "." \
|
||||||
|
-libraries "${KALEIDOSCOPE_DIR}" \
|
||||||
|
-libraries "${BOARD_HARDWARE_PATH}/.." \
|
||||||
|
${local_LIBS} \
|
||||||
|
${EXTRA_BUILDER_ARGS} \
|
||||||
|
-build-cache "${CORE_CACHE_PATH}" \
|
||||||
|
-build-path "${BUILD_PATH}" \
|
||||||
|
-ide-version "${ARDUINO_IDE_VERSION}" \
|
||||||
|
-built-in-libraries "${ARDUINO_PATH}/libraries" \
|
||||||
|
-prefs "compiler.cpp.extra_flags=${ARDUINO_CFLAGS} ${LOCAL_CFLAGS}" \
|
||||||
|
-prefs "compiler.path=${COMPILER_PATH}" \
|
||||||
|
-prefs "compiler.c.cmd=${_CMD_CC}" \
|
||||||
|
-prefs "compiler.cpp.cmd=${_CMD_CXX}" \
|
||||||
|
-prefs "compiler.ar.cmd=${_CMD_AR}" \
|
||||||
|
-prefs "compiler.c.elf.cmd=${_CMD_CXX}" \
|
||||||
|
$CCACHE_ENABLE \
|
||||||
|
-warnings all \
|
||||||
|
${ARDUINO_VERBOSE} \
|
||||||
|
${ARDUINO_AVR_GCC_PREFIX_PARAM} \
|
||||||
|
"${SKETCH_FILE_PATH}"
|
||||||
|
|
||||||
|
if [ -z "${LIBONLY}" ]; then
|
||||||
|
cp "${BUILD_PATH}/${SKETCH_FILE_NAME}.hex" "${HEX_FILE_PATH}"
|
||||||
|
cp "${BUILD_PATH}/${SKETCH_FILE_NAME}.elf" "${ELF_FILE_PATH}"
|
||||||
|
ln -sf "${OUTPUT_FILE_PREFIX}.hex" "${OUTPUT_PATH}/${SKETCH_BASE_NAME}-latest.hex"
|
||||||
|
ln -sf "${OUTPUT_FILE_PREFIX}.elf" "${OUTPUT_PATH}/${SKETCH_BASE_NAME}-latest.elf"
|
||||||
|
else
|
||||||
|
cp "${BUILD_PATH}/${SKETCH_FILE_NAME}.a" "${LIB_FILE_PATH}"
|
||||||
|
ln -sf "${OUTPUT_FILE_PREFIX}.a" "${OUTPUT_PATH}/${SKETCH_BASE_NAME}-latest.a"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${ARDUINO_VERBOSE}" = "-verbose" ]; then
|
||||||
|
echo "Build artifacts can be found in ${BUILD_PATH}";
|
||||||
|
fi
|
||||||
|
|
||||||
|
BOARD="${SAVED_BOARD}"
|
||||||
|
FQBN="${SAVED_FQBN}"
|
||||||
|
}
|
||||||
|
|
||||||
|
find_all_sketches () {
|
||||||
|
for plugin in ./*.ino \
|
||||||
|
$([ -d examples ] && find examples -name '*.ino') \
|
||||||
|
src/*.ino; do
|
||||||
|
if [ -d "$(dirname "${plugin}")" ] || [ -f "${plugin}" ]; then
|
||||||
|
p="$(basename "${plugin}" .ino)"
|
||||||
|
if [ "${p}" != '*' ]; then
|
||||||
|
case "${plugin}" in
|
||||||
|
examples/*/${p}/${p}.ino)
|
||||||
|
echo "${plugin}" | sed -e "s,examples/,," | sed -e "s,/${p}\\.ino,,"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "${p}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done | sort
|
||||||
|
}
|
||||||
|
|
||||||
|
build_all () {
|
||||||
|
plugins="$(find_all_sketches)"
|
||||||
|
|
||||||
|
for plugin in ${plugins}; do
|
||||||
|
export SKETCH="${plugin}"
|
||||||
|
$0 "${plugin}" build
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compile_all () {
|
||||||
|
plugins="$(find_all_sketches)"
|
||||||
|
|
||||||
|
for plugin in ${plugins}; do
|
||||||
|
export SKETCH="${plugin}"
|
||||||
|
$0 "${plugin}" compile
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
size () {
|
||||||
|
compile
|
||||||
|
|
||||||
|
echo "- Size: ${ELF_FILE_PATH}"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
firmware_size "${AVR_SIZE}" ${AVR_SIZE_FLAGS} "${ELF_FILE_PATH}"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
size_map () {
|
||||||
|
compile
|
||||||
|
|
||||||
|
"${AVR_NM}" --size-sort -C -r -l -t decimal "${ELF_FILE_PATH}"
|
||||||
|
}
|
||||||
|
|
||||||
|
disassemble () {
|
||||||
|
compile
|
||||||
|
|
||||||
|
"${AVR_OBJDUMP}" -C -d "${ELF_FILE_PATH}"
|
||||||
|
}
|
||||||
|
|
||||||
|
decompile () {
|
||||||
|
disassemble
|
||||||
|
}
|
||||||
|
|
||||||
|
clean () {
|
||||||
|
find_sketch
|
||||||
|
build_paths
|
||||||
|
rm -rf -- "${OUTPUT_PATH}"
|
||||||
|
rm -rf -- "${BUILD_PATH}"
|
||||||
|
kaleidoscope_dir="$(dirname "$0")/.."
|
||||||
|
if [ -d "${kaleidoscope_dir}/testing/googletest/build" ]; then
|
||||||
|
( cd "${kaleidoscope_dir}/testing/googletest/build" &&
|
||||||
|
cmake .. &&
|
||||||
|
make clean)
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_device() {
|
||||||
|
find_device_port
|
||||||
|
check_device_port
|
||||||
|
reset_device_cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
check_device_port () {
|
||||||
|
if [ -z "$DEVICE_PORT" ]; then
|
||||||
|
cat <<EOF >&2
|
||||||
|
|
||||||
|
I couldn't autodetect the keyboard's serial port.
|
||||||
|
|
||||||
|
If you see this message and your keyboard is connected to your computer,
|
||||||
|
it may mean that our serial port detection logic is buggy or incomplete.
|
||||||
|
In that case, please report this issue at:
|
||||||
|
https://github.com/keyboardio/Kaleidoscope
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
elif echo "$DEVICE_PORT" | grep -q '[[:space:]]'; then
|
||||||
|
cat <<EOF >&2
|
||||||
|
Unexpected whitespace found in detected serial port:
|
||||||
|
|
||||||
|
$DEVICE_PORT
|
||||||
|
|
||||||
|
If you see this message, it means that our serial port
|
||||||
|
detection logic is buggy or incomplete.
|
||||||
|
|
||||||
|
Please report this issue at:
|
||||||
|
https://github.com/keyboardio/Kaleidoscope
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [ -w "$DEVICE_PORT" ]; then
|
||||||
|
cat <<EOF >&2
|
||||||
|
|
||||||
|
In order to update your keyboard's firmware you need to have permission
|
||||||
|
to write to its serial port $DEVICE_PORT.
|
||||||
|
|
||||||
|
It appears that you do not have this permission:
|
||||||
|
|
||||||
|
$(ls -l "$DEVICE_PORT")
|
||||||
|
|
||||||
|
This may be because you're not in the correct unix group:
|
||||||
|
|
||||||
|
$(stat -c %G "$DEVICE_PORT").
|
||||||
|
|
||||||
|
You are currently in the following groups:
|
||||||
|
|
||||||
|
$(id -Gn)
|
||||||
|
|
||||||
|
Please ensure you have followed the instructions on setting up your
|
||||||
|
account to be in the right group:
|
||||||
|
|
||||||
|
https://github.com/keyboardio/Kaleidoscope/wiki/Install-Arduino-support-on-Linux
|
||||||
|
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
deprecation_message() {
|
||||||
|
echo "kaleidoscope-builder is deprecated and will be removed"
|
||||||
|
echo "by February 15, 2021"
|
||||||
|
echo ""
|
||||||
|
echo "To switch to the new build system, replace your sketch's"
|
||||||
|
echo "Makefile with a copy of:"
|
||||||
|
echo ""
|
||||||
|
echo "${KALEIDOSCOPE_DIR}/etc/Makefile.sketch"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
usage () {
|
||||||
|
cat <<- EOF
|
||||||
|
Usage: $0 SKETCH commands...
|
||||||
|
|
||||||
|
Runs all of the commands in the context of the Sketch.
|
||||||
|
|
||||||
|
Available commands:
|
||||||
|
|
||||||
|
help
|
||||||
|
This help screen.
|
||||||
|
|
||||||
|
compile
|
||||||
|
Compiles the sketch.
|
||||||
|
|
||||||
|
size
|
||||||
|
Reports the size of the compiled sketch.
|
||||||
|
|
||||||
|
build
|
||||||
|
Runs compile and report-size.
|
||||||
|
|
||||||
|
clean
|
||||||
|
Cleans up the output directory.
|
||||||
|
|
||||||
|
size-map
|
||||||
|
Displays the size map for the sketch.
|
||||||
|
|
||||||
|
disassemble
|
||||||
|
Decompile the sketch.
|
||||||
|
|
||||||
|
reset-device
|
||||||
|
Reset the device.
|
||||||
|
|
||||||
|
flash
|
||||||
|
Flashes the firmware using avrdude.
|
||||||
|
|
||||||
|
build-all
|
||||||
|
Build all Sketches we can find.
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
KALEIDOSCOPE_DIR="$(cd "$(dirname "$0")"/..; pwd)"
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
KALEIDOSCOPE_BIN_DIR="${KALEIDOSCOPE_DIR}/bin/"
|
||||||
|
|
||||||
|
|
||||||
|
help () {
|
||||||
|
usage
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ $# -lt 1 ]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
## Parse the command-line
|
||||||
|
## - anything that has a =, is an env var
|
||||||
|
## - from the remaining stuff, the first one is the Library/Sketch
|
||||||
|
## - everything else are commands
|
||||||
|
##
|
||||||
|
## - if there is only one argument, that's a command
|
||||||
|
|
||||||
|
|
||||||
|
deprecation_message
|
||||||
|
|
||||||
|
|
||||||
|
# shellcheck disable=SC2155
|
||||||
|
export SOURCEDIR="$(pwd)"
|
||||||
|
|
||||||
|
|
||||||
|
for conf_file in \
|
||||||
|
"${HOME}/.kaleidoscope-builder.conf" \
|
||||||
|
"${SOURCEDIR}/.kaleidoscope-builder.conf" \
|
||||||
|
"${SOURCEDIR}/kaleidoscope-builder.conf" \
|
||||||
|
"${KALEIDOSCOPE_DIR}/etc/kaleidoscope-builder.conf"; do
|
||||||
|
if [ -e "${conf_file}" ]; then
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
. "${conf_file}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
|
||||||
|
|
||||||
|
if [ -n "${VERBOSE}" ] && [[ "${VERBOSE}" -gt 0 ]]; then
|
||||||
|
ARDUINO_VERBOSE="-verbose"
|
||||||
|
else
|
||||||
|
ARDUINO_VERBOSE="-quiet"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cmds=""
|
||||||
|
|
||||||
|
## Export vars
|
||||||
|
for i in $(seq 1 $#); do
|
||||||
|
v="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
case "${v}" in
|
||||||
|
*=*)
|
||||||
|
# Exporting an expansion is *precisely* what we want here.
|
||||||
|
# shellcheck disable=SC2086,SC2163
|
||||||
|
export ${v}
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
cmds="${cmds} ${v}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Word splitting is desired here.
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
set -- ${cmds}
|
||||||
|
|
||||||
|
if [ $# -eq 1 ]; then
|
||||||
|
cmd="$(echo "$1" | tr '-' '_')"
|
||||||
|
${cmd}
|
||||||
|
exit $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
SKETCH="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
if [ "${SKETCH}" = "default" ]; then
|
||||||
|
SKETCH="${DEFAULT_SKETCH}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cmds=""
|
||||||
|
|
||||||
|
# shellcheck disable=2034
|
||||||
|
for i in $(seq 1 $#); do
|
||||||
|
cmds="${cmds} $(echo "$1" | tr '-' '_')"
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
for cmd in ${cmds}; do
|
||||||
|
${cmd}
|
||||||
|
done
|
@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
# This script sets all of the files inside src and example to have mtimes
|
||||||
|
# that match the times of the last git commit that touched each file
|
||||||
|
|
||||||
|
# This can be useful when build tools depend on file timestamps to
|
||||||
|
# make caching decisions
|
||||||
|
|
||||||
|
find src examples -type f -exec sh -c '
|
||||||
|
timestamp=$(git log --pretty=format:%ad --date=format:%Y%m%d%H%M.%S -n 1 HEAD "$1" 2> /dev/null)
|
||||||
|
if [ "x$timestamp" != "x" ]; then
|
||||||
|
touch -t "$timestamp" "$1"
|
||||||
|
fi
|
||||||
|
' sh {} \;
|
||||||
|
|
@ -1,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
|
|
@ -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": ""
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,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,88 +0,0 @@
|
|||||||
/* -*- mode: c++ -*-
|
|
||||||
* Kaleidoscope-OneShotMetaKeys -- Special OneShot keys
|
|
||||||
* 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-Macros.h>
|
|
||||||
#include <Kaleidoscope-OneShot.h>
|
|
||||||
#include <Kaleidoscope-OneShotMetaKeys.h>
|
|
||||||
|
|
||||||
// Macros
|
|
||||||
enum {
|
|
||||||
TOGGLE_ONESHOT,
|
|
||||||
};
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
KEYMAPS(
|
|
||||||
[0] = KEYMAP_STACKED
|
|
||||||
(
|
|
||||||
M(TOGGLE_ONESHOT), Key_1, Key_2, Key_3, Key_4, Key_5, OneShot_MetaStickyKey,
|
|
||||||
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,
|
|
||||||
|
|
||||||
OSM(LeftControl), Key_Backspace, OSM(LeftGui), OSM(LeftShift),
|
|
||||||
Key_Meh,
|
|
||||||
|
|
||||||
OneShot_ActiveStickyKey, Key_6, Key_7, Key_8, Key_9, Key_0, ___,
|
|
||||||
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_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
|
|
||||||
|
|
||||||
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
|
|
||||||
OSL(1)),
|
|
||||||
|
|
||||||
[1] = KEYMAP_STACKED
|
|
||||||
(
|
|
||||||
___, ___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, ___, ___, ___, ___,
|
|
||||||
|
|
||||||
___, ___, ___, ___,
|
|
||||||
___,
|
|
||||||
|
|
||||||
___, ___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, ___, ___, ___, ___,
|
|
||||||
Key_UpArrow, Key_DownArrow, Key_LeftArrow, Key_RightArrow,___, ___,
|
|
||||||
___, ___, ___, ___, ___, ___, ___,
|
|
||||||
|
|
||||||
___, ___, ___, ___,
|
|
||||||
___),
|
|
||||||
)
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
void macroToggleOneShot() {
|
|
||||||
OneShot.toggleAutoOneShot();
|
|
||||||
}
|
|
||||||
|
|
||||||
const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) {
|
|
||||||
if (macro_id == TOGGLE_ONESHOT) {
|
|
||||||
macroToggleOneShot();
|
|
||||||
}
|
|
||||||
|
|
||||||
return MACRO_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
KALEIDOSCOPE_INIT_PLUGINS(OneShot, OneShotMetaKeys, Macros);
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Kaleidoscope.setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
Kaleidoscope.loop();
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"cpu": {
|
|
||||||
"fqbn": "keyboardio:avr:model01",
|
|
||||||
"port": ""
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
// -*- mode: c++ -*-
|
|
||||||
|
|
||||||
/* This example demonstrates the Model 01 / Model 100 butterfly logo key as a
|
|
||||||
* tmux prefix key. When the key is held, Ctrl-B is pressed prior to the key
|
|
||||||
* you pressed.
|
|
||||||
*
|
|
||||||
* This example also demonstrates the purpose of using an entire layer for this
|
|
||||||
* plugin: the h/j/k/l keys in the TMUX layer are swapped for arrow keys to
|
|
||||||
* make switching between panes easier.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <Kaleidoscope.h>
|
|
||||||
#include <Kaleidoscope-PrefixLayer.h>
|
|
||||||
|
|
||||||
enum {
|
|
||||||
PRIMARY,
|
|
||||||
TMUX,
|
|
||||||
}; // layers
|
|
||||||
|
|
||||||
/* Used in setup() below. */
|
|
||||||
static const kaleidoscope::plugin::PrefixLayer::Entry prefix_layers[] PROGMEM = {
|
|
||||||
kaleidoscope::plugin::PrefixLayer::Entry(TMUX, LCTRL(Key_B)),
|
|
||||||
};
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
KEYMAPS(
|
|
||||||
[PRIMARY] = KEYMAP_STACKED
|
|
||||||
(XXX, Key_1, Key_2, Key_3, Key_4, Key_5, XXX,
|
|
||||||
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, XXX,
|
|
||||||
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,
|
|
||||||
ShiftToLayer(TMUX), Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
|
|
||||||
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
|
|
||||||
XXX),
|
|
||||||
|
|
||||||
[TMUX] = KEYMAP_STACKED
|
|
||||||
(___, ___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, ___,
|
|
||||||
___,
|
|
||||||
|
|
||||||
___, ___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, ___, ___, ___, ___,
|
|
||||||
Key_LeftArrow, Key_DownArrow, Key_UpArrow, Key_RightArrow, ___, ___,
|
|
||||||
___, ___, ___, ___, ___, ___, ___,
|
|
||||||
___, ___, ___, ___,
|
|
||||||
___),
|
|
||||||
)
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
KALEIDOSCOPE_INIT_PLUGINS(PrefixLayer);
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Kaleidoscope.setup();
|
|
||||||
/* Configure the previously-defined prefix layers. */
|
|
||||||
PrefixLayer.setPrefixLayers(prefix_layers);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
Kaleidoscope.loop();
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"cpu": {
|
|
||||||
"fqbn": "keyboardio:avr:model01",
|
|
||||||
"port": ""
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue