Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/Keystrokes/Qukeys/Qukeys.ino
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) {
}

// Use Qukeys
KALEIDOSCOPE_INIT_PLUGINS(Qukeys, Macros);
KALEIDOSCOPE_INIT_PLUGINS(Qukeys, QukeysConfig, Macros);

void setup() {
// The following Qukey definitions are for the left side of the home row (and
Expand Down
3 changes: 2 additions & 1 deletion plugins/Kaleidoscope-Qukeys/src/Kaleidoscope-Qukeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@

#pragma once

#include "kaleidoscope/plugin/Qukeys.h" // IWYU pragma: export
#include "kaleidoscope/plugin/Qukeys.h" // IWYU pragma: export
#include "kaleidoscope/plugin/QukeysConfig.h" // IWYU pragma: export
27 changes: 27 additions & 0 deletions plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ class Qukeys : public kaleidoscope::Plugin {
hold_timeout_ = hold_timeout;
}

// Get the timeout (in milliseconds) for a held qukey.
uint16_t getHoldTimeout() {
return hold_timeout_;
}

// Set the timeout (in milliseconds) for the tap-repeat feature. If a qukey is
// tapped twice in a row in less time than this amount, it will allow the user
// to hold the key with its primary value (unless it's a SpaceCadet type key,
Expand All @@ -108,6 +113,11 @@ class Qukeys : public kaleidoscope::Plugin {
tap_repeat_.timeout = ttl;
}

// Get the timeout (in milliseconds) for the tap-repeat feature.
uint8_t getMaxIntervalForTapRepeat() {
return tap_repeat_.timeout;
}

// Set the percentage of the duration of a subsequent key's press that must
// overlap with the qukey preceding it above which the qukey will take on its
// alternate key value. In other words, if the user presses qukey `Q`, then
Expand All @@ -126,19 +136,36 @@ class Qukeys : public kaleidoscope::Plugin {
}
}

// Get the overlap threshold percentage. See above for the description of the
// setting itself.
uint8_t getOverlapThreshold() {
return overlap_threshold_;
}

// Set the minimum length of time a qukey must be held before it can resolve
// to its alternate key value. If a qukey is pressed and released in less than
// this number of milliseconds, it will always produce its primary key value.
void setMinimumHoldTime(uint8_t min_hold_time) {
minimum_hold_time_ = min_hold_time;
}

// Get the minimum hold time. See the setter above for a more detailed
// description.
uint8_t getMinimumHoldTime() {
return minimum_hold_time_;
}

// Set the minimum interval between the previous keypress and the qukey press
// to make the qukey eligible to become its alternate keycode.
void setMinimumPriorInterval(uint8_t min_interval) {
minimum_prior_interval_ = min_interval;
}

// Getter for the above setter.
uint8_t getMinimumPriorInterval() {
return minimum_prior_interval_;
}

// Function for defining the array of qukeys data (in PROGMEM). It's a
// template function that takes as its sole argument an array reference of
// size `_qukeys_count`, so there's no need to use `sizeof` to calculate the
Expand Down
168 changes: 168 additions & 0 deletions plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/QukeysConfig.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/* -*- mode: c++ -*-
* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2023 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/plugin/Qukeys.h"
#include "kaleidoscope/plugin/QukeysConfig.h"

#include <Arduino.h> // for F
#include <Kaleidoscope-EEPROM-Settings.h> // for EEPROMSettings
#include <Kaleidoscope-FocusSerial.h> // for Focus, FocusSerial
#include <stdint.h> // for uint16_t, uint32_t, uint8_t

#include "kaleidoscope/Runtime.h" // for Runtime, Runtime_
#include "kaleidoscope/device/device.h" // for VirtualProps::Storage, Base<>::Storage
#include "kaleidoscope/event_handler_result.h" // for EventHandlerResult, EventHandlerResult::OK

namespace kaleidoscope {
namespace plugin {

void QukeysConfig::loadSettings() {
Settings_ settings;

Runtime.storage().get(settings_addr_, settings);
::Qukeys.setHoldTimeout(settings.hold_timeout_);
::Qukeys.setOverlapThreshold(settings.overlap_threshold_);
::Qukeys.setMinimumHoldTime(settings.minimum_hold_time_);
::Qukeys.setMinimumPriorInterval(settings.minimum_prior_interval_);
::Qukeys.setMaxIntervalForTapRepeat(settings.tap_repeat_interval_);
}

void QukeysConfig::updateSettings() {
Settings_ settings;

settings.hold_timeout_ = ::Qukeys.getHoldTimeout();
settings.overlap_threshold_ = ::Qukeys.getOverlapThreshold();
settings.minimum_hold_time_ = ::Qukeys.getMinimumHoldTime();
settings.minimum_prior_interval_ = ::Qukeys.getMinimumPriorInterval();
settings.tap_repeat_interval_ = ::Qukeys.getMaxIntervalForTapRepeat();

Runtime.storage().put(settings_addr_, settings);
Runtime.storage().commit();
}

EventHandlerResult QukeysConfig::onSetup() {
settings_addr_ = ::EEPROMSettings.requestSlice(sizeof(Settings_));

// If the EEPROM is empty, store the default settings.
if (Runtime.storage().isSliceUninitialized(settings_addr_, sizeof(Settings_))) {
updateSettings();
}

loadSettings();
return EventHandlerResult::OK;
}

EventHandlerResult QukeysConfig::onNameQuery() {
return ::Focus.sendName(F("QukeysConfig"));
}

EventHandlerResult QukeysConfig::onFocusEvent(const char *input) {
enum Command : uint8_t {
HOLD_TIMEOUT,
MAX_INTERVAL_FOR_TAP_REPEAT,
OVERLAP_THRESHOLD,
MINIMUM_HOLD_TIME,
MINIMUM_PRIOR_INTERVAL,
} cmd;
const char *cmd_hold_timeout = PSTR("qukeys.hold_timeout");
const char *cmd_max_tap_repeat_interval = PSTR("qukeys.max_tap_repeat_interval");
const char *cmd_overlap_threshold = PSTR("qukeys.overlap_threshold");
const char *cmd_minimum_hold_time = PSTR("qukeys.minimum_hold_time");
const char *cmd_minimum_prior_interval = PSTR("qukeys.minimum_prior_interval");

if (::Focus.inputMatchesHelp(input))
return ::Focus.printHelp(
cmd_hold_timeout,
cmd_max_tap_repeat_interval,
cmd_overlap_threshold,
cmd_minimum_hold_time,
cmd_minimum_prior_interval);

if (::Focus.inputMatchesCommand(input, cmd_hold_timeout))
cmd = Command::HOLD_TIMEOUT;
else if (::Focus.inputMatchesCommand(input, cmd_max_tap_repeat_interval))
cmd = Command::MAX_INTERVAL_FOR_TAP_REPEAT;
else if (::Focus.inputMatchesCommand(input, cmd_overlap_threshold))
cmd = Command::OVERLAP_THRESHOLD;
else if (::Focus.inputMatchesCommand(input, cmd_minimum_hold_time))
cmd = Command::MINIMUM_HOLD_TIME;
else if (::Focus.inputMatchesCommand(input, cmd_minimum_prior_interval))
cmd = Command::MINIMUM_PRIOR_INTERVAL;
else
return EventHandlerResult::OK;

if (::Focus.isEOL()) {
// If there are no arguments, send back the current configuration values.
switch (cmd) {
case Command::HOLD_TIMEOUT:
::Focus.send(::Qukeys.getHoldTimeout());
break;
case Command::MAX_INTERVAL_FOR_TAP_REPEAT:
::Focus.send(::Qukeys.getMaxIntervalForTapRepeat());
break;
case Command::OVERLAP_THRESHOLD:
::Focus.send(::Qukeys.getOverlapThreshold());
break;
case Command::MINIMUM_HOLD_TIME:
::Focus.send(::Qukeys.getMinimumHoldTime());
break;
case Command::MINIMUM_PRIOR_INTERVAL:
::Focus.send(::Qukeys.getMinimumPriorInterval());
break;
default:
// if a valid command is issued but there is no 0-arg handler for it,
// we stop processing the event.
return EventHandlerResult::ABORT;
}
} else {
switch (cmd) {
uint16_t v;
uint8_t b;

case Command::HOLD_TIMEOUT:
::Focus.read(v);
::Qukeys.setHoldTimeout(v);
break;
case Command::MAX_INTERVAL_FOR_TAP_REPEAT:
::Focus.read(b);
::Qukeys.setMaxIntervalForTapRepeat(b);
break;
case Command::OVERLAP_THRESHOLD:
::Focus.read(b);
::Qukeys.setOverlapThreshold(b);
break;
case Command::MINIMUM_HOLD_TIME:
::Focus.read(b);
::Qukeys.setMinimumHoldTime(b);
break;
case Command::MINIMUM_PRIOR_INTERVAL:
::Focus.read(b);
::Qukeys.setMinimumPriorInterval(b);
break;
default:
return EventHandlerResult::ABORT;
}
updateSettings();
}

return EventHandlerResult::EVENT_CONSUMED;
}

} // namespace plugin
} // namespace kaleidoscope

kaleidoscope::plugin::QukeysConfig QukeysConfig;
58 changes: 58 additions & 0 deletions plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/QukeysConfig.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* -*- mode: c++ -*-
* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2023 Keyboard.io, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include <stdint.h> // for uint16_t

#include "kaleidoscope/event_handler_result.h" // for EventHandlerResult
#include "kaleidoscope/plugin.h" // for Plugin

namespace kaleidoscope {
namespace plugin {

// =============================================================================
// Plugin for configuration of Qukeys via Focus and persistent storage of
// settins in EEPROM (i.e. Chrysalis).
class QukeysConfig : public Plugin {
public:
EventHandlerResult onNameQuery();
EventHandlerResult onSetup();
EventHandlerResult onFocusEvent(const char *command);

private:
void loadSettings();
void updateSettings();

// The base address in persistent storage for configuration data:
uint16_t settings_addr_;

// struct used to temporarily cache settings
struct Settings_ {
uint16_t hold_timeout_;
uint8_t overlap_threshold_;
uint8_t minimum_hold_time_;
uint8_t minimum_prior_interval_;
uint8_t tap_repeat_interval_;
};
};

} // namespace plugin
} // namespace kaleidoscope

extern kaleidoscope::plugin::QukeysConfig QukeysConfig;