-
-
Notifications
You must be signed in to change notification settings - Fork 40.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[New Feature] Custom Modified Values #2900
Conversation
This was resulting in build errors when travis CI builds all keyboards.
Looks like there is a conflict that needs manual resolution. |
- Completely reviewed the architecture of the feature. - Oneshot modifiers are supported in CMV-v2. - Updated CMV feature documentation. - Deleted the atom47.h file which was causing conflicts
Designed a much cleaner and lighter (~400 bytes) version ( |
- Removed CMV_NUMBER_OF_HELD_KEYS from the CMV-2.0 version - Updated documentation
To make the reviewers' job easier and for those of you interested in understanding how the feature works, I'm going to explain the The magic happens mainly in // in quantum/keymap_common.c near line 232
__attribute__ ((weak))
uint16_t keymap_key_to_keycode(uint8_t layer, keypos_t key)
{
#if defined(CUSTOM_MODIFIED_VALUES_ENABLE)
return keymap_key_to_keycode_cmv(layer, key);
#else
// Read entire word (16bits)
return pgm_read_word(&keymaps[(layer)][(key.row)][(key.col)]);
#endif
} Fisrt thing we do in // in quantum/keymap_common.c near line 220
static uint16_t keymap_key_to_keycode_cmv(uint8_t layer, keypos_t key) {
uint8_t kcid = get_kcid(get_custom_modified_values_for_key(pgm_read_word(&keymaps[layer][key.row][key.col]), layer, key));
if (kcid) {
set_mods_blocker(true);
} else {
set_mods_blocker(false);
}
return cmv_buffer[kcid];
} The function // in quantum/keymap_common.c near line 205
static uint16_t* get_custom_modified_values_for_key(uint16_t default_kc, uint8_t layer, keypos_t key) {
if(keycodes_for_key(default_kc, layer, key)) {
set_cmv_buffer(default_kc, 0, 0, 0);
}
return cmv_buffer;
} Inside // in quantum/custom_modified_values.h near line 13
#define CMV(kc_default, kc_shifted, kc_altgred, kc_sftralt) set_cmv_buffer(kc_default, kc_shifted, kc_altgred, kc_sftralt) The // in quantum/keymap_common.c near line 190
static uint16_t cmv_buffer[4] = {0,0,0,0}; So back in // in quantum/keymap_common.c near line 212
static uint8_t get_kcid(uint16_t* kcs) {
if(are_there_non_charmods()) return 0;
else if(are_there_shifts() && are_there_ralts() && kcs[3]) return 3;
else if(are_there_shifts() && kcs[1]) return 1;
else if(are_there_ralts() && kcs[2]) return 2;
return 0;
} (Note: I think it is a good idea to return the default keycode (i.e. KCID=0) if there are any non-charmods modifiers active, so the user can still use shortcuts such as The // in tmk_core/common/action_util.c near line 69
// #define CMV_NON_CHARMODS_MASK (0b00000000 | MOD_BIT(KC_RGUI) | MOD_BIT(KC_RCTRL) | MOD_BIT(KC_LGUI) | MOD_BIT(KC_LALT) | MOD_BIT(KC_LCTRL))
#define CMV_NON_CHARMODS_MASK 0b10011101
// #define CMV_SHIFTS_MASK (0b00000000 | MOD_BIT(KC_RSHIFT) | MOD_BIT(KC_LSHIFT))
#define CMV_SHIFTS_MASK 0b00100010
// #define CMV_RALT_MASK (0b00000000 | MOD_BIT(KC_RALT))
#define CMV_RALT_MASK 0b01000000
#ifndef NO_ACTION_ONESHOT
# define ARE_THERE_THESE_MODS(mods_mask) ( \
(mods_mask & real_mods) || \
(mods_mask & macro_mods) || \
(mods_mask & oneshot_mods) || \
(mods_mask & oneshot_locked_mods))
#else
# define ARE_THERE_THESE_MODS(mods_mask) ((mods_mask & real_mods) || (mods_mask & macro_mods))
#endif
bool are_there_non_charmods(void) { return ARE_THERE_THESE_MODS(CMV_NON_CHARMODS_MASK); }
bool are_there_shifts(void) { return ARE_THERE_THESE_MODS(CMV_SHIFTS_MASK); }
bool are_there_ralts(void) { return ARE_THERE_THESE_MODS(CMV_RALT_MASK); } Back in // in tmk_core/common/action_util.c near line 86
static bool dont_send_mods = false; This variable is then used inside the // in tmk_core/common/action_util.c near line 187
void send_keyboard_report(void) {
#ifdef CUSTOM_MODIFIED_VALUES_ENABLE
if (dont_send_mods) {
keyboard_report->mods = weak_mods;
# ifndef NO_ACTION_ONESHOT
if (oneshot_mods) {
# if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0))
if (has_oneshot_mods_timed_out()) {
dprintf("Oneshot: timeout\n");
clear_oneshot_mods();
}
# endif
if (has_anykey(keyboard_report)) {
clear_oneshot_mods();
host_keyboard_send(keyboard_report); // These two lines are here to
clear_keys(); // prevent the keycode to get stuck
}
}
# endif
} else {
#endif
keyboard_report->mods = real_mods;
keyboard_report->mods |= weak_mods;
keyboard_report->mods |= macro_mods;
#ifndef NO_ACTION_ONESHOT
if (oneshot_mods) {
#if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0))
if (has_oneshot_mods_timed_out()) {
dprintf("Oneshot: timeout\n");
clear_oneshot_mods();
}
#endif
keyboard_report->mods |= oneshot_mods;
if (has_anykey(keyboard_report)) {
clear_oneshot_mods();
}
}
#endif
#ifdef CUSTOM_MODIFIED_VALUES_ENABLE
}
#endif
host_keyboard_send(keyboard_report);
} Finally, a last problem needed to be solved: if the user release a key at a moment where the modifiers' state is different than it was when the key was pressed, it is most likely that the returned keycode will not be the same as the one returned when the key was pressed in the first place which will result in a stuck key. To solve this issue without having to store the keycodes processed when the key was pressed (that would make the feature notably heavier (this is done in the // in tmk_core/common/action_util.c near line 221
void add_mods(uint8_t mods) {
#ifdef CUSTOM_MODIFIED_VALUES_ENABLE
clear_keys();
#endif
real_mods |= mods;
}
void del_mods(uint8_t mods) {
#ifdef CUSTOM_MODIFIED_VALUES_ENABLE
clear_keys();
#endif
real_mods &= ~mods;
} (Please take note that this solves the problem for MOST of the keycodes but the problem can still potentially occur for some advanced keycodes. If you're absolutely willing to use these latter keycodes, please consider allocating a little more space to the feature and use the |
Re-reviewing this..... this shouldn't be handled as a define, but as a full on feature . Eg, you should add this to the ifeq ($(strip $(CUSTOM_MODIFIED_VALUES_ENABLE)), yes)
OPT_DEFS += -DCUSTOM_MODIFIED_VALUES_ENABLE
endif Additionally, it may be a good idea to rebase, as it's been a while. |
This was resulting in build errors when travis CI builds all keyboards.
- Completely reviewed the architecture of the feature. - Oneshot modifiers are supported in CMV-v2. - Updated CMV feature documentation. - Deleted the atom47.h file which was causing conflicts
- Removed CMV_NUMBER_OF_HELD_KEYS from the CMV-2.0 version - Updated documentation
As drashna suggested here qmk#2900 (comment)
I think I correctly added CMV as a full on feature, however I can't be 100% sure. |
I found a simpler way to do that: #define maybe_add_weak_mods(keycode, mod) \
if (keycode < QK_MODS_MAX && \
(keycode & 0xff00) == QK_ ## mod) \
add_weak_mods(MOD_BIT(KC_ ## mod))
static bool alternate_modifier(uint16_t modifier, uint16_t keycode, keyrecord_t *record) {
static bool in_alternate_modifier;
/* when it's a key press and modifier state is pressed */
if (record->event.pressed && (get_mods() & MOD_BIT(modifier))) {
in_alternate_modifier = true;
/* will send modifier up so that the os won't shift the keycode we will send */
del_mods(MOD_BIT(modifier));
/* send mods if keycode needs it */
maybe_add_weak_mods(keycode, LCTL);
maybe_add_weak_mods(keycode, LSFT);
maybe_add_weak_mods(keycode, LALT);
maybe_add_weak_mods(keycode, LGUI);
maybe_add_weak_mods(keycode, RCTL);
maybe_add_weak_mods(keycode, RSFT);
maybe_add_weak_mods(keycode, RALT);
maybe_add_weak_mods(keycode, RGUI);
/* send mods modifications */
send_keyboard_report();
/* send alternate key code */
register_code(keycode);
/* we changed the internal state by releasing the modifier key, marked against
as pressed so that we are back in the real state */
add_mods(MOD_BIT(modifier));
return false;
}
/* when releasing the key and we activated alternate modifier */
if (!record->event.pressed && in_alternate_modifier) {
in_alternate_modifier = false;
/* release the alternate key */
unregister_code(keycode);
/* make sure all mods we sat up earlier are released */
clear_weak_mods();
/* send mods modification */
send_keyboard_report();
return false;
}
return true;
}
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch(keycode) {
case BP_UNDS:
return alternate_modifier(KC_LSFT, BP_EXCLAIM, record);
case SHF_LPRN:
return alternate_modifier(KC_LSFT, BP_RPRN, record);
case BP_LCBR:
return alternate_modifier(KC_LSFT, BP_RCBR, record);
case BP_LBRC:
return alternate_modifier(KC_LSFT, BP_RBRC, record);
case BP_LESS:
return alternate_modifier(KC_LSFT, BP_GRTR, record);
}
return true;
} |
I took the code for my personal keymap. Hope to see it as an official feature soon. |
@@ -201,7 +201,7 @@ bool process_record_quantum(keyrecord_t *record) { | |||
keypos_t key = record->event.key; | |||
uint16_t keycode; | |||
|
|||
#if !defined(NO_ACTION_LAYER) && !defined(STRICT_LAYER_RELEASE) | |||
#if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be reverted. There is no more PSM, as it is now the default behavior.
#if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS) | |
#if !defined(NO_ACTION_LAYER) && !defined(STRICT_LAYER_RELEASE) |
@@ -120,7 +120,7 @@ void process_hand_swap(keyevent_t *event) { | |||
} | |||
#endif | |||
|
|||
#if !defined(NO_ACTION_LAYER) && !defined(STRICT_LAYER_RELEASE) | |||
#if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS) | |
#if !defined(NO_ACTION_LAYER) && !defined(STRICT_LAYER_RELEASE) |
@@ -62,7 +62,7 @@ void action_function(keyrecord_t *record, uint8_t id, uint8_t opt); | |||
bool process_record_quantum(keyrecord_t *record); | |||
|
|||
/* Utilities for actions. */ | |||
#if !defined(NO_ACTION_LAYER) && !defined(STRICT_LAYER_RELEASE) | |||
#if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS) | |
#if !defined(NO_ACTION_LAYER) && !defined(STRICT_LAYER_RELEASE) |
@@ -228,7 +228,7 @@ void layer_debug(void) | |||
} | |||
#endif | |||
|
|||
#if !defined(NO_ACTION_LAYER) && !defined(STRICT_LAYER_RELEASE) | |||
#if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS) | |
#if !defined(NO_ACTION_LAYER) && !defined(STRICT_LAYER_RELEASE) |
@@ -272,7 +272,7 @@ uint8_t read_source_layers_cache(keypos_t key) | |||
*/ | |||
action_t store_or_get_action(bool pressed, keypos_t key) | |||
{ | |||
#if !defined(NO_ACTION_LAYER) && !defined(STRICT_LAYER_RELEASE) | |||
#if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS) | |
#if !defined(NO_ACTION_LAYER) && !defined(STRICT_LAYER_RELEASE) |
@@ -88,7 +88,7 @@ uint32_t layer_state_set_user(uint32_t state); | |||
uint32_t layer_state_set_kb(uint32_t state); | |||
|
|||
/* pressed actions cache */ | |||
#if !defined(NO_ACTION_LAYER) && !defined(STRICT_LAYER_RELEASE) | |||
#if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS) | |
#if !defined(NO_ACTION_LAYER) && !defined(STRICT_LAYER_RELEASE) |
@@ -0,0 +1,444 @@ | |||
# CMV (Custom Modified Values) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to add a link to _summary.md
and _sidebar.md
for this page to show up in docs correctly.
If I may: I suggest implementing the code I proposed in my previous comment here instead of applying this change request. The code is simpler, cleaner and less intrusive. I can create a pull request for it but then we first need to discuss what goes where. |
By the way, I wanted to thank you @canatella for contributing, with code that indeed look much simpler and cleaner. In fact I would think at first glance that it is too easy, in the sense that it is not implementing every functionality I originally thought would go into that new feature. Although, I haven't got any time to get my head into it and actually review your code lately. I still won't be able to focus on this until next week but I'd be glad to discuss with you then, about what should be in the feature, how we should implement it, etc. |
I'm just gonna start by listing all the functionalities my version of the feature has so we can see whether or not they are supported by yours and discuss if they should or not.
@canatella If you could confirm which of these functionalities are currently supported by your version of the feature, so we could discuss about those which are not and if they are dispensable or not. |
It uses process_record_user so yes you just setup the cmv on the key you desire: you either switch on the keycode or if you want precise control you can test record.event.key.pos.
It's a macro so you can do anything you could do with a macro, instead of my function taking an keycode argument, it could call process_modified_record_user, where you could put the exact same code that you can use in process_record_user. The function I propose mainly just check if a modifier is set, if so unregister it, then do the stuff the user want to do then register the modifer again.
Like I said, the code I'm using is in process_record_user, so you can do anything you could do in process_record_user.
It seems you can trigger one shot event from macros with set_one_shot_* functions so I suppose it could be supported.
The code is checking for 1 modifier, you could easily replace the conditional to check for two or more. I think it would even be simpler and you could simplify your code too with a simple addition to qmk. @drashna, in users/drashna/drashna.h, you define the MOD_*_MASK macros, would you mind puting them in the qmk core code? They are several place in the code were With that in place, the alternate_modifier function could take a mod mask as argument instead of a modifier key and then could be used to trigger action on multiple modifiers.
Could you explain a bit more what do you mean here? To be honest, I really don't care about which code goes into qmk. It was a bit complicated to understand how your code is working and it goes somewhat deep into the qmk core code to do something that's simple enough to do from process_record_user. Granted it might not support all the feature but it is easy to understand and to hack and if, instead of calling all the maybe_add_weak_mods and the register_code, I'd call a user function, then any macro code could be run instead of just sending a keycode. If it does not provide all the feature you need then go ahead. The alternate code is small enough to copy paste in any keymap anyway. In the end it would be nice to have the maintainers advice on this topic. |
I stand corrected! Now that you explain it, your version is indeed much better, much cleaner and easily hackable moreover.
My first versions of the feature had a problem: If you pressed a key (say In conclusion, I think your version should be the one included in the firmware. So, I think the easiest way to do that is you should emit your own pull request for your code so we can close this one and redirect to yours. Thank you very much for your contribution :) |
see pr #4795 |
CMV is a feature that allows the user to customize the keycodes processed by the firmware and sent to the host depending on if
Left Shift
and/orRight Shift
and/orRight Alt
are being pressed or not.For example, the user could assign the following to a key:
The feature is documented in more details in
docs/feature_custom_modified_values.md
and theCMV-1.0-Lite
version weights about 1350 bytes in the hex file.CMV-2.0-Lite
weights about 400 bytesThis the feature that would solve the Issue #1495.