Skip to content
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

[Core] Add Repeat Key ("repeat last key") as a core feature. #19700

Merged
merged 47 commits into from
May 20, 2023

Conversation

getreuer
Copy link
Contributor

This PR implements Repeat Key as a core feature. I previously implemented a userspace Repeat Key implementation that this one is based on.

Description

The Repeat Key performs the action of the last pressed key. There are two ergonomically-grounded motivations for Repeat Key:

  • As pointed out by NotGate, it is attractive from the point of view that double-tapped letters are like same-finger bigrams (SFBs): "The repeat key fits the theme of reducing SFBs by as much as possible and has the bonus of including the 10th finger. Words like follow become 'fol[rep]ow' so your flow is never broken by the speed of a single finger."

  • It is useful for hotkey chords, like repeating Ctrl+Shift+Right to select by word or repeating Ctrl+PageUp / Ctrl+PageDown to switch browser tabs.

On that second point, hotkeys are quite often in pairs, navigating or acting in one direction or the reverse. This feature also includes a Reverse Repeat Key for such situations. The Reverse Repeat Key performs the "reverse" of the last key, if a reverse is defined. When the last key wast Ctrl+Shift+Right, the Reverse Repeat Key performs Ctrl+Shift+Left. Together with the Repeat Key, this enables convenient selection by words in either direction.

The implementation interoperates well with other features. It is essentially a one-key version of how dynamic macros work: pressing the Repeat Key (or Reverse Repeat Key) generates a keyrecord_t and passes it to process_record(). This interoperates predictably with all QMK features that I have tested it with. This includes, for example, macros with custom handlers in process_record_user(): tapping the macro key and then Repeat Key invokes the macro twice.

The implementation is space efficient. On my keymap, it adds 686 bytes to the firmware size. If Reverse Repeat Key is disabled (there is a NO_REVERSE_REPEAT_KEY option), the cost reduces to 350 bytes.

Please see the documentation docs/feature_repeat_key.md for further description.

Types of Changes

  • Core
  • Bugfix
  • New feature
  • Enhancement/optimization
  • Keyboard (addition or update)
  • Keymap/layout/userspace (addition or update)
  • Documentation

Checklist

  • My code follows the code style of this project: C, Python
  • I have read the PR Checklist document and have made the appropriate changes.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • I have tested the changes and verified that they work and don't break anything (as well as I can manage).

This commit adds Repeat Key as a core feature, including documentation
and tests. The implementation is based on the userspace implementation
<https://getreuer.info/posts/keyboards/repeat-key>.

Differences in this implementation:

* When generating repeated key events, my userspace implementation
  temporarily sets the layer state to what it was when the key was
  originally pressed. This implementation instead leverages the .keycode
  field in the keyrecord_t so that the temporary layer state switching
  isn't needed.

* Thanks to the previous point, this implementation is able to add a
  `set_repeat_key_keycode(kc)` API.

* This implementation removes repeat_key_register() and
  repeat_key_unregister(). I realize these APIs raise more trouble than
  they're probably worth, since it's easy to accidentally make a key
  that uselessly attempts to repeat itself.

* I omit the "Reverse Repeat Key." On the one hand, I haven't yet found
  a nice way to supply a default for the "reverse key pairs table." On
  the other hand, it is fairly doable given Repeat Key's
  `get_repeat_key_keycode()` to implement a Reverse Repeat Key in
  userspace.
@getreuer
Copy link
Contributor Author

getreuer commented Feb 4, 2023

Here is an outline for navigating the code changes in this PR.

Setup

  • I defined two new keycodes QK_REP and QK_RREP in
    keycodes_0.0.2_quantum.hjson and ran qmk generate-keycodes -v latest.

  • Repeat Key leverages the .keycode field in keyrecord_t.
    This field currently exists only when Combos are enabled, so I modified
    action.h and action.c where needed to use this field when either Combos or
    Repeat Key (or both features) are enabled.

Sequence of events for Repeat Key

  1. On every key press, process_repeat_key() calls
    get_repeat_key_eligible() to check whether the key is "eligible"
    for repeating.

  2. If so, its keycode and keyrecord as well as the current mods
    state are stored. The keycode is saved in the .keycode field (this matters
    if the layer state changes between now and when Repeat is used).

  3. When Repeat is pressed (or released), repeat_key_invoke() is called.

  4. repeat_key_invoke() creates a keyrecord by combining the stored keyrecord
    with the current keyevent so that the time and keypos correspond to when
    Repeat was used.

  5. The keyrecord is passed to process_record() to execute its effect.

Reverse Repeat

Reverse Repeat works the same way except for the keycode. When Reverse
Repeat is pressed, the keyrecord's .keycode field is set to
get_rev_repeat_key_keycode(), getting the "reverse" of the last key.

@getreuer getreuer requested a review from zvecr February 8, 2023 23:32
@drashna
Copy link
Member

drashna commented Feb 10, 2023

qmk generate-keycodes-tests -v latest should be ran too.

@getreuer
Copy link
Contributor Author

qmk generate-keycodes-tests -v latest should be ran too.

Thanks, @drashna! I updated test_common/keycode_table.cpp.

FWIW, I just learned there is a handy script util/regen.sh that runs all keycode regenerators.

@drashna drashna requested a review from a team February 11, 2023 18:03
@drashna
Copy link
Member

drashna commented Feb 12, 2023

Ah, the test function PR was merged. I wasn't sure if it was or not yet.

But yeah, that works for that.

That said, there are some merge conflicts here, as well as keycode value collisions due to the tri layer PR getting merged first.

@getreuer
Copy link
Contributor Author

Done, merged with the develop branch. It's interesting to see the tri layer changes.

@Ikcelaks
Copy link

I am currently running this branch and using both the repeat key and reverse repeat key in my layout. Thus far both have worked exactly as I expected them to.

I have no comments on the code, but I do question the naming of the Reverse Repeat functionality. This key can be fully dynamic based on the last key press and need not be constrained to logically reversing that keypress. In my keymap, I think of it as the "magic" key, and have built a dictionary that eliminates the worst single finger bigrams that would otherwise exist on the layout. I suggest that the keycode be something like QK_DYNAMIC_REPEAT to reflect that it functions like the repeat key, except that the output is fully dynamic based on your definition of get_rev_repeat_key_keycode_user().

I think it would be worth adding some documentation that highlights this use-case, even if it is still called Reverse Repeat.

@getreuer
Copy link
Contributor Author

@Ikcelaks thanks for checking out the PR and this excellent point! You are right, Reverse Repeat can be used for much more than "reversing", and this is well worth clarifying. Leveraging it to remove SFBs is an awesome idea, you inspire me to experiment with that.

I renamed Reverse Repeat to Alternate Repeat (and "rev_repeat_key" APIs renamed to "alt_repeat_key) to suggest a wider scope including the original "reverse" use case as well as dynamic or complementary actions more generally.

@getreuer
Copy link
Contributor Author

getreuer commented Apr 3, 2023

To follow up, the recent revisions to get_repeat_key_eligible_user() and get_alt_repeat_key_keycode_user() are now analogously done in my userspace Repeat Key implementation in getreuer/qmk-keymap@cab494f.

@Ikcelaks thank you for that context on STRD1 and Magic Sturdy! I found your Magic Sturdy writeup, thanks for doing this. You have gained a user! I've been test driving Magic Sturdy since Friday. As luck would have it, I've been experimenting with a Sturdy mod that was only a few keyswaps away, so I'm getting up to speed quickly and excited to begin getting a "taste of the power" with the right index magic key. That "-ion" shortcut is very nice.

@drashna drashna requested a review from a team April 12, 2023 16:03
@getreuer
Copy link
Contributor Author

I'm a GitHub noob and have a question. I made the changes that @sigprof requested, but the PR status still shows "Changes requested." Should I be clicking something to clear that, or is that up to reviewers to resolve?

Screenshot

In any case, this PR is ready to resume review.

@Ikcelaks
Copy link

Ikcelaks commented May 2, 2023

I see that this PR is tagged for breaking_change_2023q2. Does this mean that it's scheduled to make a particular release? Is there anything needed from a testing perspective that I can help with?

I've been running my Moonlander on this PR branch for the past couple months, and it has worked splendidly. If there is concern about how this might interact with other features, I would be willing to test. The masses need access to this incredibly powerful feature.

@getreuer
Copy link
Contributor Author

getreuer commented May 3, 2023

@Ikcelaks thank you so much for your support and offer to help! Testing interaction with other features would absolutely be appreciated. I also welcome your review comments if you have further thoughts on the code.

I see that this PR is tagged for breaking_change_2023q2. Does this mean that it's scheduled to make a particular release?

I described in my previous comment that the PR status shows "Changes requested". I have made those changes, but I am a GitHub noob and not certain what the process is from here. I think the next step is that @sigprof would at their discretion clear the "Changes requested" state in a re-review. Let me know if I'm missing something!

Besides this, yes, @drashna applied the "breaking_change_2023q2" tag. My understanding is that this is tentative, since changes to core require at least two approvers to merge. So far, drashna has approved and requested review from other core QMK members. Thank you, drashna!

@drashna
Copy link
Member

drashna commented May 6, 2023

I'm a GitHub noob and have a question. I made the changes that @sigprof requested, but the PR status still shows "Changes requested." Should I be clicking something to clear that, or is that up to reviewers to resolve?

Nope, don't need to do anything. Github ... is a bit odd in some places. The sidebar with reviews is more accurate, as the "changes requested" part doesn't actually get updated until a new review is added by that reviewer or is manually cleared. It's a bit odd.

I see that this PR is tagged for breaking_change_2023q2. Does this mean that it's scheduled to make a particular release? Is there anything needed from a testing perspective that I can help with?

It's a way for us to filter PRs, and give a bit higher priority to getting them reviewed, so it's more of a soft target for them.

And yeah, generally want two reviews for PRs

@getreuer
Copy link
Contributor Author

getreuer commented May 7, 2023

@drashna thanks for clearing that up! Good to hear the PR is on track, and I appreciate that the breaking_change_2023q2 tag improves review priority a bit.

@Ikcelaks
Copy link

@getreuer can you merge in the dev branch again at your next convenience?

I thought that I found a bad interaction with Combos, but it turns out that my testing instead ran into the regression bug recently fixed by #20669.

Otherwise, I haven't run into any issues.

@getreuer
Copy link
Contributor Author

@Ikcelaks thank you for the testing and the heads up! I just did a merge. What was the scenario with Combos that interacted badly? I'd like to add a unit test for that.

getreuer added 3 commits May 14, 2023 16:39
The last key tracking in Repeat Key's implementation is useful for
things far removed from Repeat Key. Referring to APIs with "repeat_key"
in the name is out of place and potentially confusing. It also occurred
to me that future QMK features might want to share this last key logic,
for instance if the Adaptive Keys PR qmk#14034
is revived, in which case it would help to name the APIs relating to the
last key more generically and separately from Repeat Key.

This commit renames

* get_repeat_key_eligible_user() -> remember_last_key_user()
* get_repeat_key_keycode() -> get_last_keycode()
* set_repeat_key_keycode() -> set_last_keycode()
* get_repeat_key_mods() -> get_last_mods()
* set_repeat_key_mods() -> set_last_mods()

and segments last key logic to a separate process_last_key() handler.
@Ikcelaks
Copy link

Ikcelaks commented May 15, 2023

@Ikcelaks thank you for the testing and the heads up! I just did a merge. What was the scenario with Combos that interacted badly? I'd like to add a unit test for that.

There is no bad interaction. I was just encountering the independent regression that is now fixed. I have verified that my combos are now working as expected.

I like the new function names. In particular get_repeat_key_eligible_user() was confusing, since it was also used to modify how the key was remembered. remember_last_key_user is so much clearer.

@tzarc tzarc merged commit 3993b15 into qmk:develop May 20, 2023
@drashna
Copy link
Member

drashna commented May 20, 2023

looks like repeat_key_combo unit test is failing due to #19670

Edit: fixed in #21005

getreuer added a commit to getreuer/qmk-keymap that referenced this pull request May 21, 2023
The last key tracking in Repeat Key's implementation is useful for
things far removed from Repeat Key. Referring to APIs with "repeat_key"
in the name is out of place and potentially confusing.

This commit renames

* get_repeat_key_eligible_user() -> remember_last_key_user()
* get_repeat_key_keycode() -> get_last_keycode()
* set_repeat_key_keycode() -> set_last_keycode()
* get_repeat_key_mods() -> get_last_mods()
* set_repeat_key_mods() -> set_last_mods()

This matches the API names in the upcoming Repeat Key implementation in
QMK core qmk/qmk_firmware#19700
@getreuer getreuer deleted the core/repeat_key branch May 21, 2023 21:14
coquizen pushed a commit to coquizen/qmk_firmware that referenced this pull request Jun 22, 2023
Co-authored-by: casuanoob <96005765+casuanoob@users.noreply.github.com>
Co-authored-by: Sergey Vlasov <sigprof@gmail.com>
autoferrit pushed a commit to SpaceRockMedia/bastardkb-qmk that referenced this pull request Dec 8, 2023
Co-authored-by: casuanoob <96005765+casuanoob@users.noreply.github.com>
Co-authored-by: Sergey Vlasov <sigprof@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants