Adds oneshot layer and oneshot tap toggling (#308)

This commit is mostly a cherry-pick from `ahtn` at
https://github.com/tmk/tmk_keyboard/pull/255.

These are the changes:

* Adds ACTION_LAYER_ONESHOT
* Adds ONESHOT_TAP_TOGGLE
* Mentions sticky keys in the docs on oneshot.
pull/313/head
Thiago Alves 9 years ago committed by Jack Humbert
parent d4520cd3ac
commit 74e97eefd7

@ -74,6 +74,7 @@ void process_action_kb(keyrecord_t *record) {}
void process_action(keyrecord_t *record) void process_action(keyrecord_t *record)
{ {
bool do_release_oneshot = false;
keyevent_t event = record->event; keyevent_t event = record->event;
#ifndef NO_ACTION_TAPPING #ifndef NO_ACTION_TAPPING
uint8_t tap_count = record->tap.count; uint8_t tap_count = record->tap.count;
@ -81,6 +82,13 @@ void process_action(keyrecord_t *record)
if (IS_NOEVENT(event)) { return; } if (IS_NOEVENT(event)) { return; }
#if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0))
if (has_oneshot_layer_timed_out()) {
dprintf("Oneshot layer: timeout\n");
clear_oneshot_layer_state(ONESHOT_OTHER_KEY_PRESSED);
}
#endif
process_action_kb(record); process_action_kb(record);
action_t action = store_or_get_action(event.pressed, event.key); action_t action = store_or_get_action(event.pressed, event.key);
@ -95,6 +103,15 @@ void process_action(keyrecord_t *record)
// clear the potential weak mods left by previously pressed keys // clear the potential weak mods left by previously pressed keys
clear_weak_mods(); clear_weak_mods();
} }
#ifndef NO_ACTION_ONESHOT
// notice we only clear the one shot layer if the pressed key is not a modifier.
if (is_oneshot_layer_active() && event.pressed && !IS_MOD(action.key.code)) {
clear_oneshot_layer_state(ONESHOT_OTHER_KEY_PRESSED);
do_release_oneshot = !is_oneshot_layer_active();
}
#endif
switch (action.kind.id) { switch (action.kind.id) {
/* Key and Mods */ /* Key and Mods */
case ACT_LMODS: case ACT_LMODS:
@ -139,24 +156,37 @@ void process_action(keyrecord_t *record)
// Oneshot modifier // Oneshot modifier
if (event.pressed) { if (event.pressed) {
if (tap_count == 0) { if (tap_count == 0) {
dprint("MODS_TAP: Oneshot: 0\n");
register_mods(mods); register_mods(mods);
} } else if (tap_count == 1) {
else if (tap_count == 1) {
dprint("MODS_TAP: Oneshot: start\n"); dprint("MODS_TAP: Oneshot: start\n");
set_oneshot_mods(mods); set_oneshot_mods(mods);
} #if defined(ONESHOT_TAP_TOGGLE) && ONESHOT_TAP_TOGGLE > 1
else { } else if (tap_count == ONESHOT_TAP_TOGGLE) {
dprint("MODS_TAP: Toggling oneshot");
clear_oneshot_mods();
set_oneshot_locked_mods(mods);
register_mods(mods);
#endif
} else {
register_mods(mods); register_mods(mods);
} }
} else { } else {
if (tap_count == 0) { if (tap_count == 0) {
clear_oneshot_mods(); clear_oneshot_mods();
unregister_mods(mods); unregister_mods(mods);
} } else if (tap_count == 1) {
else if (tap_count == 1) {
// Retain Oneshot mods // Retain Oneshot mods
} #if defined(ONESHOT_TAP_TOGGLE) && ONESHOT_TAP_TOGGLE > 1
else { if (mods & get_mods()) {
clear_oneshot_locked_mods();
clear_oneshot_mods();
unregister_mods(mods);
}
} else if (tap_count == ONESHOT_TAP_TOGGLE) {
// Toggle Oneshot Layer
#endif
} else {
clear_oneshot_mods(); clear_oneshot_mods();
unregister_mods(mods); unregister_mods(mods);
} }
@ -309,6 +339,44 @@ void process_action(keyrecord_t *record)
event.pressed ? layer_move(action.layer_tap.val) : event.pressed ? layer_move(action.layer_tap.val) :
layer_clear(); layer_clear();
break; break;
#ifndef NO_ACTION_ONESHOT
case OP_ONESHOT:
// Oneshot modifier
#if defined(ONESHOT_TAP_TOGGLE) && ONESHOT_TAP_TOGGLE > 1
do_release_oneshot = false;
if (event.pressed) {
del_mods(get_oneshot_locked_mods());
if (get_oneshot_layer_state() == ONESHOT_TOGGLED) {
reset_oneshot_layer();
layer_off(action.layer_tap.val);
break;
} else if (tap_count < ONESHOT_TAP_TOGGLE) {
layer_on(action.layer_tap.val);
set_oneshot_layer(action.layer_tap.val, ONESHOT_START);
}
} else {
add_mods(get_oneshot_locked_mods());
if (tap_count >= ONESHOT_TAP_TOGGLE) {
reset_oneshot_layer();
clear_oneshot_locked_mods();
set_oneshot_layer(action.layer_tap.val, ONESHOT_TOGGLED);
} else {
clear_oneshot_layer_state(ONESHOT_PRESSED);
}
}
#else
if (event.pressed) {
layer_on(action.layer_tap.val);
set_oneshot_layer(action.layer_tap.val, ONESHOT_START);
} else {
clear_oneshot_layer_state(ONESHOT_PRESSED);
if (tap_count > 1) {
clear_oneshot_layer_state(ONESHOT_OTHER_KEY_PRESSED);
}
}
#endif
break;
#endif
default: default:
/* tap key */ /* tap key */
if (event.pressed) { if (event.pressed) {
@ -372,6 +440,18 @@ void process_action(keyrecord_t *record)
default: default:
break; break;
} }
#ifndef NO_ACTION_ONESHOT
/* Because we switch layers after a oneshot event, we need to release the
* key before we leave the layer or no key up event will be generated.
*/
if (do_release_oneshot && !(get_oneshot_layer_state() & ONESHOT_PRESSED ) ) {
record->event.pressed = false;
layer_on(get_oneshot_layer());
process_action(record);
layer_off(get_oneshot_layer());
}
#endif
} }
@ -560,6 +640,7 @@ bool is_tap_key(keypos_t key)
switch (action.layer_tap.code) { switch (action.layer_tap.code) {
case 0x00 ... 0xdf: case 0x00 ... 0xdf:
case OP_TAP_TOGGLE: case OP_TAP_TOGGLE:
case OP_ONESHOT:
return true; return true;
} }
return false; return false;

@ -76,7 +76,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
* 101E|LLLL|1111 0001 On/Off (0xF1) [NOT TAP] * 101E|LLLL|1111 0001 On/Off (0xF1) [NOT TAP]
* 101E|LLLL|1111 0010 Off/On (0xF2) [NOT TAP] * 101E|LLLL|1111 0010 Off/On (0xF2) [NOT TAP]
* 101E|LLLL|1111 0011 Set/Clear (0xF3) [NOT TAP] * 101E|LLLL|1111 0011 Set/Clear (0xF3) [NOT TAP]
* 101E|LLLL|1111 xxxx Reserved (0xF4-FF) * 101E|LLLL|1111 0100 One Shot Layer (0xF4) [TAP]
* 101E|LLLL|1111 xxxx Reserved (0xF5-FF)
* ELLLL: layer 0-31(E: extra bit for layer 16-31) * ELLLL: layer 0-31(E: extra bit for layer 16-31)
* *
* *
@ -250,6 +251,7 @@ enum layer_pram_tap_op {
OP_ON_OFF, OP_ON_OFF,
OP_OFF_ON, OP_OFF_ON,
OP_SET_CLEAR, OP_SET_CLEAR,
OP_ONESHOT,
}; };
#define ACTION_LAYER_BITOP(op, part, bits, on) (ACT_LAYER<<12 | (op)<<10 | (on)<<8 | (part)<<5 | ((bits)&0x1f)) #define ACTION_LAYER_BITOP(op, part, bits, on) (ACT_LAYER<<12 | (op)<<10 | (on)<<8 | (part)<<5 | ((bits)&0x1f))
#define ACTION_LAYER_TAP(layer, key) (ACT_LAYER_TAP<<12 | (layer)<<8 | (key)) #define ACTION_LAYER_TAP(layer, key) (ACT_LAYER_TAP<<12 | (layer)<<8 | (key))
@ -266,6 +268,7 @@ enum layer_pram_tap_op {
#define ACTION_LAYER_ON_OFF(layer) ACTION_LAYER_TAP((layer), OP_ON_OFF) #define ACTION_LAYER_ON_OFF(layer) ACTION_LAYER_TAP((layer), OP_ON_OFF)
#define ACTION_LAYER_OFF_ON(layer) ACTION_LAYER_TAP((layer), OP_OFF_ON) #define ACTION_LAYER_OFF_ON(layer) ACTION_LAYER_TAP((layer), OP_OFF_ON)
#define ACTION_LAYER_SET_CLEAR(layer) ACTION_LAYER_TAP((layer), OP_SET_CLEAR) #define ACTION_LAYER_SET_CLEAR(layer) ACTION_LAYER_TAP((layer), OP_SET_CLEAR)
#define ACTION_LAYER_ONESHOT(layer) ACTION_LAYER_TAP((layer), OP_ONESHOT)
#define ACTION_LAYER_MODS(layer, mods) ACTION_LAYER_TAP((layer), 0xe0 | ((mods)&0x0f)) #define ACTION_LAYER_MODS(layer, mods) ACTION_LAYER_TAP((layer), 0xe0 | ((mods)&0x0f))
/* With Tapping */ /* With Tapping */
#define ACTION_LAYER_TAP_KEY(layer, key) ACTION_LAYER_TAP((layer), (key)) #define ACTION_LAYER_TAP_KEY(layer, key) ACTION_LAYER_TAP((layer), (key))

@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "report.h" #include "report.h"
#include "debug.h" #include "debug.h"
#include "action_util.h" #include "action_util.h"
#include "action_layer.h"
#include "timer.h" #include "timer.h"
static inline void add_key_byte(uint8_t code); static inline void add_key_byte(uint8_t code);
@ -47,11 +48,70 @@ report_keyboard_t *keyboard_report = &(report_keyboard_t){};
#ifndef NO_ACTION_ONESHOT #ifndef NO_ACTION_ONESHOT
static int8_t oneshot_mods = 0; static int8_t oneshot_mods = 0;
static int8_t oneshot_locked_mods = 0;
int8_t get_oneshot_locked_mods(void) { return oneshot_locked_mods; }
void set_oneshot_locked_mods(int8_t mods) { oneshot_locked_mods = mods; }
void clear_oneshot_locked_mods(void) { oneshot_locked_mods = 0; }
#if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0)) #if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0))
static int16_t oneshot_time = 0; static int16_t oneshot_time = 0;
inline bool has_oneshot_mods_timed_out() {
return TIMER_DIFF_16(timer_read(), oneshot_time) >= ONESHOT_TIMEOUT;
}
#endif #endif
#endif #endif
/* oneshot layer */
#ifndef NO_ACTION_ONESHOT
/* oneshot_layer_data bits
* LLLL LSSS
* where:
* L => are layer bits
* S => oneshot state bits
*/
static int8_t oneshot_layer_data = 0;
inline uint8_t get_oneshot_layer(void) { return oneshot_layer_data >> 3; }
inline uint8_t get_oneshot_layer_state(void) { return oneshot_layer_data & 0b111; }
#if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0))
static int16_t oneshot_layer_time = 0;
inline bool has_oneshot_layer_timed_out() {
return TIMER_DIFF_16(timer_read(), oneshot_layer_time) >= ONESHOT_TIMEOUT &&
!(get_oneshot_layer_state() & ONESHOT_TOGGLED);
}
#endif
/* Oneshot layer */
void set_oneshot_layer(uint8_t layer, uint8_t state)
{
oneshot_layer_data = layer << 3 | state;
layer_on(layer);
#if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0))
oneshot_layer_time = timer_read();
#endif
}
void reset_oneshot_layer(void) {
oneshot_layer_data = 0;
#if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0))
oneshot_layer_time = 0;
#endif
}
void clear_oneshot_layer_state(oneshot_fullfillment_t state)
{
uint8_t start_state = oneshot_layer_data;
oneshot_layer_data &= ~state;
if (!get_oneshot_layer_state() && start_state != oneshot_layer_data) {
layer_off(get_oneshot_layer());
#if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0))
oneshot_layer_time = 0;
#endif
}
}
bool is_oneshot_layer_active(void)
{
return get_oneshot_layer_state();
}
#endif
void send_keyboard_report(void) { void send_keyboard_report(void) {
keyboard_report->mods = real_mods; keyboard_report->mods = real_mods;
@ -60,7 +120,7 @@ void send_keyboard_report(void) {
#ifndef NO_ACTION_ONESHOT #ifndef NO_ACTION_ONESHOT
if (oneshot_mods) { if (oneshot_mods) {
#if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0)) #if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0))
if (TIMER_DIFF_16(timer_read(), oneshot_time) >= ONESHOT_TIMEOUT) { if (has_oneshot_mods_timed_out()) {
dprintf("Oneshot: timeout\n"); dprintf("Oneshot: timeout\n");
clear_oneshot_mods(); clear_oneshot_mods();
} }
@ -70,6 +130,7 @@ void send_keyboard_report(void) {
clear_oneshot_mods(); clear_oneshot_mods();
} }
} }
#endif #endif
host_keyboard_send(keyboard_report); host_keyboard_send(keyboard_report);
} }
@ -143,11 +204,12 @@ void clear_oneshot_mods(void)
oneshot_time = 0; oneshot_time = 0;
#endif #endif
} }
uint8_t get_oneshot_mods(void)
{
return oneshot_mods;
}
#endif #endif
/* /*
* inspect keyboard state * inspect keyboard state
*/ */

@ -56,10 +56,30 @@ void clear_macro_mods(void);
/* oneshot modifier */ /* oneshot modifier */
void set_oneshot_mods(uint8_t mods); void set_oneshot_mods(uint8_t mods);
uint8_t get_oneshot_mods(void);
void clear_oneshot_mods(void); void clear_oneshot_mods(void);
void oneshot_toggle(void); void oneshot_toggle(void);
void oneshot_enable(void); void oneshot_enable(void);
void oneshot_disable(void); void oneshot_disable(void);
bool has_oneshot_mods_timed_out(void);
int8_t get_oneshot_locked_mods(void);
void set_oneshot_locked_mods(int8_t mods);
void clear_oneshot_locked_mods(void);
typedef enum {
ONESHOT_PRESSED = 0b01,
ONESHOT_OTHER_KEY_PRESSED = 0b10,
ONESHOT_START = 0b11,
ONESHOT_TOGGLED = 0b100
} oneshot_fullfillment_t;
void set_oneshot_layer(uint8_t layer, uint8_t state);
uint8_t get_oneshot_layer(void);
void clear_oneshot_layer_state(oneshot_fullfillment_t state);
void reset_oneshot_layer(void);
bool is_oneshot_layer_active(void);
uint8_t get_oneshot_layer_state(void);
bool has_oneshot_layer_timed_out(void);
/* inspect */ /* inspect */
uint8_t has_anykey(void); uint8_t has_anykey(void);

@ -528,14 +528,20 @@ This is a feature to assign both toggle layer and momentary switch layer action
### 4.3 Oneshot Modifier ### 4.3 Oneshot Modifier
This runs onetime effects which modify only on just one following key. It works as normal modifier key when holding down while oneshot modifier when tapping. This runs onetime effects which modify only on just one following key. It works as normal modifier key when holding down while oneshot modifier when tapping. The behavior of oneshot modifiers is similar to the [sticky keys](https://en.wikipedia.org/wiki/StickyKeys) functionality found in most operating systems.
ACTION_MODS_ONESHOT(MOD_LSFT) ACTION_MODS_ONESHOT(MOD_LSFT)
Oneshot layer key:
ACTION_LAYER_ONESHOT(MY_LAYER)
Say you want to type 'The', you have to push and hold Shift key before type 't' then release it before type 'h' and 'e', otherwise you'll get 'THe' or 'the' unintentionally. With Oneshot Modifier you can tap Shift then type 't', 'h' and 'e' normally, you don't need to holding Shift key properly here. This mean you can release Shift before 't' is pressed down. Say you want to type 'The', you have to push and hold Shift key before type 't' then release it before type 'h' and 'e', otherwise you'll get 'THe' or 'the' unintentionally. With Oneshot Modifier you can tap Shift then type 't', 'h' and 'e' normally, you don't need to holding Shift key properly here. This mean you can release Shift before 't' is pressed down.
Oneshot effect is cancel unless following key is pressed down within `ONESHOT_TIMEOUT` of `config.h`. No timeout when it is `0` or not defined. Oneshot effect is cancel unless following key is pressed down within `ONESHOT_TIMEOUT` of `config.h`. No timeout when it is `0` or not defined.
Most implementations of sticky keys allow you to lock a modifier by double tapping the modifier. The layer then remains locked untill the modifier is tapped again. To enable this behaviour for oneshot modifiers set `ONESHOT_TAP_TOGGLE` to the number taps required. The feature is disabled if `ONESHOT_TAP_TOGGLE<2` or not defined.
### 4.4 Tap Toggle Mods ### 4.4 Tap Toggle Mods
Similar to layer tap toggle, this works as a momentary modifier when holding, but toggles on with several taps. A single tap will 'unstick' the modifier again. Similar to layer tap toggle, this works as a momentary modifier when holding, but toggles on with several taps. A single tap will 'unstick' the modifier again.

Loading…
Cancel
Save