I have both = and + bound to seperate keys in my keymap. When I first press one of them, then press the other while the first key is still being held, the second keypress does not register.
If I press the second key a second time (or keep holding it) while the first key is still being pressed, then those presses are registered.
I found this behaviour while typing +=. Apparently I often still have my finger on + while already typing the =. I would expect to immediately get the = like it is the case with other keys.
This also happens with other keycodes that are basically shifted/unshifted versions of the same key, such as { and [.
Until I find a better way to do this, I wrote this in my custom keymap:
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
if ((keycode & QK_LSFT) == QK_LSFT || (keycode & QK_RSFT) == QK_RSFT) {
if (record->event.pressed) {
keycode &= ~(QK_LSFT | QK_RSFT);
add_mods(MOD_BIT(KC_LSFT));
add_key(keycode);
send_keyboard_report();
del_key(keycode);
del_mods(MOD_BIT(KC_LSFT));
send_keyboard_report();
}
return false;
}
return true;
};
It will intercept any events with shifted keys (e.g. ~, {}, <>).
The only downside I've noticed so far is that you can't hold the keys, but that's still way better than the alternative.
I can confirm this behavior. Really odd.
I tried your approach, @oscar-broman , but it had not the desired effect.
Does anyone know why this happens?
Why it happens is rather simple:
If you press KC_TILDE (~), then it will press LShift + `. This is because KC_TILDE is defined to be LSHIFT(KC_GRAVE).
Thus, holding ~ will cause LShift and ` to be held down until the key is released. If you press = before releasing ~ then LShift will be held, which will type out + instead of =.
But that is not what is happening. The second key is simply omitted until you press that same key again or when keeping it pressed (all while the first key is being held down). The same thing also happens when the order is switched (press =, then +). It's not like the second key comes out as its shifted version.
But I guess you're right in that it apparently has something to do with the shift modifier.
I see what you mean now. With my changes, I can properly type += (rather than ++) but it still doesn't fix =+, which is typed out only as =.
Edit: I just realized the reason this is happening is very simple.. When = is pressed, it will map to = being pressed. Pressing +means that keys Shift and = will be pressed. If you're already holding down = then the only keystroke that will be sent is Shift.
This solves all the problems mentioned here. Any key in auto_tap_keys will be tapped. This also works for combinations with modifiers.
There are probably much better ways to do this. I've only glanced at this codebase so far..
#define COUNT(x) (sizeof (x) / sizeof (*(x)))
const uint16_t PROGMEM auto_tap_keys[] = {
KC_EQUAL,
KC_LBRACKET,
KC_RBRACKET,
};
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
bool auto_tap = false;
for (int i = 0; i < COUNT(auto_tap_keys); i++) {
if ((keycode & 0xFF) == auto_tap_keys[i]) {
auto_tap = true;
break;
}
}
if (auto_tap) {
if (!record->event.pressed) {
return false;
}
uint8_t mods = 0;
if (keycode & QK_LSFT) mods |= MOD_BIT(KC_LSFT);
if (keycode & QK_RSFT) mods |= MOD_BIT(KC_RSFT);
if (keycode & QK_LCTL) mods |= MOD_BIT(KC_LCTL);
if (keycode & QK_RCTL) mods |= MOD_BIT(KC_RCTL);
if (keycode & QK_LGUI) mods |= MOD_BIT(KC_LGUI);
if (keycode & QK_RGUI) mods |= MOD_BIT(KC_RGUI);
if (keycode & QK_LALT) mods |= MOD_BIT(KC_LALT);
if (keycode & QK_RALT) mods |= MOD_BIT(KC_RALT);
keycode &= ~(QK_LSFT | QK_RSFT | QK_LCTL | QK_RCTL | QK_LGUI | QK_RGUI | QK_LALT | QK_RALT);
uint8_t prev_mods = get_mods();
set_mods(mods);
add_key(keycode);
send_keyboard_report();
del_key(keycode);
clear_mods();
send_keyboard_report();
set_mods(prev_mods);
send_keyboard_report();
return false;
}
return true;
};
I think I know, what's going on, but I will need to look a bit deeper, so I assigned this to myself.
I finally managed to try out the workaround provided by @oscar-broman. Seems to work quiet well, except for the mentioned issue, that you can't hold down the keys in auto_tap_keys anymore. Still much better than before. Thanks for sharing the code.
@fredizzimo if you need someone to test out some of your ideas, let me know!
Thanks @oscar-broman your solution works works for fast typing DE_LESS(<) and DE_MORE(>) but it blocks the usage DE_PIPE(|) which is the same base key but with an RALT modifier.
QMKs german keymap defines those keys as following
// Alt gr
#define ALGR(kc) RALT(kc)
#define DE_ALGR KC_RALT
#define DE_LESS KC_NUBS // < and > and |
#define DE_MORE LSFT(DE_LESS) // >
#define DE_PIPE ALGR(DE_LESS) // |
and I use the your solution with the following auto_tap_keys[] array
const uint16_t PROGMEM auto_tap_keys[] = {
DE_LESS,
};
Neither using DE_PIPE in the keymap directly nor pressing KC_RALT + DE_LESS result in a | typed with your solution enabled.
@raetiacorvus The way it should be done is by putting DE_PIPE into auto_tap_keys, and into the keymap. Did you try that?
Only key combinations should be in auto_tap_keys.
Only key combinations should be in auto_tap_keys.
Now I am confused. You have KC_EQUAL in there which is a non shifted key and not KC_PLUS which i would have seen as key combination.
Putting DE_PIPE (RALT(DE_LESS)) or DE_MORE (LSFT(DE_LESS)) in auto_tap_keys does not change the behavior at all while with DE_LESS in there at least typing > while < is hold down and vice versa works.
As a workaround for me I disabled the script for RALT modified keys.
…
if (auto_tap) {
if (keycode & QK_RALT) return true;
if (!record->event.pressed) {
return false;
}
…
Not yet sure why but the problem is in this block:
if (keycode & QK_LSFT) mods |= MOD_BIT(KC_LSFT);
if (keycode & QK_RSFT) mods |= MOD_BIT(KC_RSFT);
if (keycode & QK_LCTL) mods |= MOD_BIT(KC_LCTL);
if (keycode & QK_LGUI) mods |= MOD_BIT(KC_LGUI);
if (keycode & QK_RGUI) mods |= MOD_BIT(KC_RGUI);
if (keycode & QK_LALT) mods |= MOD_BIT(KC_LALT);
if (keycode & QK_RALT) mods |= MOD_BIT(KC_RALT);
if (keycode & QK_RCTL) mods |= MOD_BIT(KC_RCTL);
while trying to debug this i noticed that on an RALT(kc) it also detects a QK_RSFT which in turn results in my problem.
Because with the following everything works fine, so I guess something goes wrong with the mod detection.
if (keycode & QK_RALT) {
mods |= MOD_BIT(KC_RALT);
} else {
if (keycode & QK_LSFT) mods |= MOD_BIT(KC_LSFT);
if (keycode & QK_RSFT) mods |= MOD_BIT(KC_RSFT);
if (keycode & QK_LCTL) mods |= MOD_BIT(KC_LCTL);
if (keycode & QK_LGUI) mods |= MOD_BIT(KC_LGUI);
if (keycode & QK_RGUI) mods |= MOD_BIT(KC_RGUI);
if (keycode & QK_LALT) mods |= MOD_BIT(KC_LALT);
if (keycode & QK_RALT) mods |= MOD_BIT(KC_RALT);
if (keycode & QK_RCTL) mods |= MOD_BIT(KC_RCTL);
}
Well one of the problems is, that modifier bits are overlapping so if i understand it right (keycode & QK_LSFT) will be true when even only partial bits overlap. Shouldn't it be ((keycode & QK_LSFT) == QK_LSFT) e.g. QK_LSFT is a subset of keycode?
edit: no that also isn't the solution because now we still detect subsets of that modifiert.
e.g. left alt is a subset of right alt in this case.
QK_RALT 0001 0100 0000 0000
QK_LALT 0000 0100 0000 0000
So here is the fixed version with the downside, that only left or right version of a modifier gets pressed at the same time.
const uint16_t PROGMEM auto_tap_keys[] = {
//Put your auto tap base key here
};
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
bool auto_tap = false;
for (int i = 0; i < COUNT(auto_tap_keys); i++) {
if ((keycode & 0xFF) == auto_tap_keys[i]) {
auto_tap = true;
break;
}
}
if (auto_tap) {
if (!record->event.pressed) {
return false;
}
uint8_t mods = 0;
if ((keycode & QK_RSFT) == QK_RSFT) {
mods |= MOD_BIT(KC_RSFT);
} else if ((keycode & QK_LSFT) == QK_LSFT) {
mods |= MOD_BIT(KC_LSFT);
}
if ((keycode & QK_RCTL) == QK_RCTL) {
mods |= MOD_BIT(KC_RCTL);
} else if ((keycode & QK_LCTL) == QK_LCTL) {
mods |= MOD_BIT(KC_LCTL);
}
if ((keycode & QK_RGUI) == QK_RGUI) {
mods |= MOD_BIT(KC_RGUI);
} else if ((keycode & QK_LGUI) == QK_LGUI) {
mods |= MOD_BIT(KC_LGUI);
}
if ((keycode & QK_RALT) == QK_RALT) {
mods |= MOD_BIT(KC_RALT);
} else if ((keycode & QK_LALT) == QK_LALT) {
mods |= MOD_BIT(KC_LALT);
}
keycode &= ~(QK_LSFT | QK_RSFT | QK_LCTL | QK_RCTL | QK_LGUI | QK_RGUI | QK_LALT | QK_RALT);
uint8_t prev_mods = get_mods();
set_mods(mods);
add_key(keycode);
send_keyboard_report();
del_key(keycode);
clear_mods();
send_keyboard_report();
set_mods(prev_mods);
send_keyboard_report();
return false;
}
return true;
}
So here is the fixed version with the downside, that only left or right version of a modifier gets pressed at the same time.
So... considering that the "IS_COMMAND" variable is defined as Left AND Right Shift, that would break that, wouldn't it?
Meaning no way to toggle NKRO, or other bootmagic stuff, correct?
So... considering that the "IS_COMMAND" variable is defined as Left AND Right Shift, that would break that, wouldn't it?
Hey I just tried to fix @oscar-broman code so it at least won't register wrong modifiers and works for my case. I am happy if you can add anything to improve it.
Meaning no way to toggle NKRO, or other bootmagic stuff, correct?
Only if you put any of the magic keys into auto_tap_keys
You can change my code so on right modifier presses both get pressed by changing the else if to ifs. Otherwise you can change your magic keys if they collide or change your magic keycombo to something like left shift + right ctrl.
Another less automated solution could be to use custom keycodes like
…
enum auto_tap_keycodes {
AT_LESS = SAFE_RANGE,
AT_MORE,
AT_PIPE,
};
…
void auto_tap_key(uint16_t keycode, uint8_t mods) {
uint8_t prev_mods = get_mods();
set_mods(mods);
add_key(keycode);
send_keyboard_report();
del_key(keycode);
clear_mods();
send_keyboard_report();
set_mods(prev_mods);
send_keyboard_report();
}
…
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case AT_LESS:
if (record->event.pressed) {
uint8_t mods = 0;
auto_tap_key(DE_LESS, mods);
}
return false;
case AT_MORE:
if (record->event.pressed) {
uint8_t mods = MOD_BIT(KC_LSFT);
auto_tap_key(DE_LESS, mods);
}
return false;
case AT_PIPE:
if (record->event.pressed) {
uint8_t mods = MOD_BIT(KC_RALT);
auto_tap_key(DE_LESS, mods);
}
return false;
}
return true;
}
But hey I am not really well versed in QMKs codebase so I am happy for any constructive critic.
@drashna is there a way to properly distinguish between all 8 modifiers from the keycode itself? Am I missing something?
Sorry!
Yes and no. Yes, you can distinguish all of the mods. But left vs right is an issue, IIRC.
The PR by @fredizzimo fixes the issue for some keys (as described, same keycode but different modifiers), but it still doesn't fix the issue where pressing & followed by = will result in &+ instead of &=.
My code doesn't work in the latest version, it seems, and I'm not sure why! I also can't compile older versions with my GCC so I'm stuck with my laptop keyboard at the moment!
Just dropping a note here in case it's useful to anyone else: My current workaround for the above is to abuse SEND_STRING for the tap semantics https://github.com/callum-oakley/qmk_firmware/blob/master/keyboards/planck/keymaps/callum/keymap.c#L259-L320. It isn't pretty but it does the job nicely.
Neat! My current solution is similar to yours, although not as clean:
void tap_key(uint16_t keycode, uint8_t mods) {
uint8_t prev_mods = get_mods();
clear_mods();
clear_keyboard();
add_key(keycode);
if (mods) set_mods(mods);
send_keyboard_report();
del_key(keycode);
clear_mods();
send_keyboard_report();
set_mods(prev_mods);
send_keyboard_report();
};
// Usage:
tap_key(KC_COMM, MOD_BIT(KC_LSFT)); // <