-
Notifications
You must be signed in to change notification settings - Fork 79
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
Added support for Sequences (aka macros) #30
base: master
Are you sure you want to change the base?
Conversation
…events are executed in succession.
Please fmt your code. |
src/layout.rs
Outdated
/// Press event with just a keycode (for sequences) | ||
SequencePress(KeyCode), | ||
/// Release event with just a keycode (for sequences) | ||
SequenceRelease(KeyCode), |
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 can't modify this enum. It's the public contract of events entering a layout. This enum is not non_exhaustive
, and thus adding variants is a breaking change.
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.
are breaking changes bad for keyboard firmware - I'm build and flash it all in one go so from my side I wouldn't be affected by a breaking change? - maybe we should break now, up the semver and make it non exhaustive at the same time?
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.
Breaking change will say that you don't to just do cargo update to update keyberon. I don't care of making breaking changes, but it must add something to the user.
But here, the problem is more that implementation details leak in the public interface.
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.
@gilescope I think TeXitoi was just making a point (in a nice, technical way) that how I implemented this particular feature sucks, haha. He's right though! I've got a much better solution that I'm currently working on that should allow us to incorporate specific delays between keystrokes instead of just one universal delay that's the same for all Press()
and Release()
events (in a given macro).
I'm still new to Rust and his wisdom is appreciated 😄 . I'm currently struggling with writing code (a map()
, filter()
or filiter_map()
) that figures out which keycodes need to remain down during the execution of any given sequence. It's not so simple because if you have a Press()
but no corresponding Release()
until the end of the sequence you need to A) figure out where in the sequence you are and B) cancel out matching pairs of Press()
and Release()
events but only if they're within the same window of the sequence (that you're currently at).
I actually got the code working to execute simple sequences but it currently doesn't work with modifiers or anything that needs to remain held during the sequence. Hopefully not much longer now!
Aside: In the process of writing this I've learned a lot about how HID keyboards work, haha. Like how you can only have a maximum of six keys down at a time--even though there's no real technical reason behind this limitation. It's just that when folks were defining the USB HID spec they didn't say how many keycodes could be sent at once so vendors (e.g. Apple and Microsoft) limited it to six simultaneous keycodes in their implementations (or at least, that's how it was explained to me). No idea what happens if you try to send more but it's in my TODO list of things to play with =)
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.
The "usb keyboard boot protocol" has this 6 keys (+8 modifiers) limitation. There is alternative USB descriptor, but the one used in keyberon has this limitation (that I didn't find limiting, so I didn't try the "NKRO" descriptor support).
…events are executed in succession.
…lso added a new Delay() feature that lets you put arbitrary delays between sequence (key macro) events.
All fixed I hope! I followed your instructions as best I understood them and also made const MACROTEST2: Action = Sequence {
events: &[
Press(A), Release(A),
Delay {since: 0, ticks: 5000},
Press(F), Release(F),
],
}; Maybe it could use a shortcut function? |
Just want to mention #10 so this PR gets tracked in that issue. |
Please run fmt and fix clippy warnings. |
I made the changes necessary to make Clippy happy but they weren't to my code... That's why I left them alone previously (because I didn't want to mess up your stuff--in case you intentionally wanted the code to be as it was). Anyways, all set! |
You still have a lot of not fmted code. For the clippy warning, for e in iter {
// something
break;
} is equivalent to if let Some(e) = iter.next() {
// something
} |
Aha! Thanks to some help in Discord I've successfully made Clippy happy. I was so confused on that one, haha. I also re-ran both the |
as well as the ability to use a `Complete` as a sort of "release all".
In my testing I accidentally added a sequence with a ridiculously long delay (two extra 0s haha) and it occurred to me that there's no mechanism to cancel a running sequence without hitting the reset switch. To solve that problem I just added a |
Anything else you need me to change? The code is passing all tests and has had rustfmt run. I'll change whatever else you want just need some direction 👍 |
Sorry, not so much time to pause and carefully read this PR. Hopefully soon, I will not forget. |
as a shortcut for `Press(),Release()`
For such a case, maybe that's even better to have some kind of recording directly in the keyboard. As you described, that's a very complicated feature, needing json parsing and layout modification on the fly. |
Shall I get started on that then? 😄 |
self.keycodes() | ||
} | ||
/// Takes care of draining and populating the `sequenced` ArrayDeque, | ||
/// giving us sequences (aka macros) of nearly limitless length! | ||
fn process_sequences(&mut self) { |
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 can really simplify this. You only need one variant at a time, having a deque of 16 is really overkill for memory, and for complexity of code.
You can do everything with
cur_event: Option<Event>,
awaiting_events: &'static [Event],
And when you need another event, something like
match self.awaiting_events {
[e, tail @ ..] => {
self.cur_event = Some(e);
self.awaiting_event = tail;
}
[] => (),
}
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.
I'll see if I can make that work... I actually tried something like this initially but couldn't get past the borrow checker complaining about not knowing the length of the events ahead of time... If I only had one sequence I could get it to work but if there were multiple sequences of different lengths it would complain because the size of the events array would be different every time.
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 can also have a ArrayDeque<&'static [Event]>
if you need several slots.
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.
https://doc.rust-lang.org/stable/std/primitive.slice.html#method.split_first is more appropriate.
src/layout.rs
Outdated
// Copy the first chunk of `SequenceEvent`s into the `sequenced` ArrayDeque | ||
// ...we'll cover the remaining chunks as we drain them from `sequenced` | ||
let chunk_length = self.sequenced.capacity() - 1; // -1: Keep a slot for Continue() | ||
if let Some(chunk) = events.chunks(chunk_length).next() { |
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.
My proposition will simplify this.
And what if a sequence is executed during the execution of another sequence? It need a test.
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.
Yeah, multiple simultaneous sequences is an edge case in the TODO list. The way it works right now is that if you kick off a sequence while another one is already running it ends the current sequence and starts the new one. I made the sequenced
ArrayDeque
16 items long originally so I could have multiple simultaneous sequences running but the way the logic works right now that can't work. I had planned to use the length of sequenced
divided by four so you could have up to four simultaneous sequences going on but the amount of logic that would need to change for that to happen was too much (for the current state of the PR--can't have everything at the start of these things).
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 can document the actual behavior, and add an issue.
Any problem with the revîew? Do you need some help to fix it? I hope I didn't discourage you with this review 😇 |
You haven't discouraged me... It just took a while for the initial review that I started work on other things 😄 I'm finishing up those other things (tease: https://imgur.com/a/IFsvBGh) and will be returning to this in the next few days. I did start work on it though... Already implemented a decrementing counter instead of using the since/ticks method 👍 |
…6a3aadba5caf0474998e1c2eb69af11&dn=Les.Blagues.De.Toto.2020.FRENCH.1080p.WEB.x264-PREUMS.mkv&tr=udp://tracker.openbittorrent.com:80&tr=udp://tracker.opentrackr.org:1337/announce
Added support for multiple simultaneous sequences executed at once (max 4). Renamed CancelSequence to CancelSequences since we now do up to 4 at once.
I'm done making my keyboard PCB and was able to get back to work on this! I implemented all of your suggested changes and added some general improvements (can handle up to four simultaneous running sequences now!). Had a hell of a time with the merge from upstream though haha. Hopefully I didn't clobber anything! Double-check that please 😄 I've got complete unit tests for everything except multiple simultaneous sequences running at the same time because I'm not sure how to handle that (in test cases). |
Just checking in... In case you missed Github's notification that I've submitted a new update 😁 |
Seen, just not so much time to work on this. I also have to implement tap hod, and release v0.2 |
I just merged upstream (everything's up to date again). Any chance you can take a look? |
I really want a clean public interface to merge it. Implementation details can be changed later, so OK to merge without the other proposed change. |
OK I think I've resolved the "clean public interface" issue which was the last remaining hurdle (I think). @TeXitoi What do you think? |
Demo: https://youtu.be/M2OXrNP3fgI
Example usage:
I couldn't get working
SequenceEvent::ReleaseAll()
(see: https://github.com/riskable/keyberon/blob/master/src/layout.rs#L324) working. Probably a trivial task for you =)Also, I'm not sure what the purpose is of that
transform()
method (it's not used anywhere) so I just guessed in my own implementation, haha. I also didn't implementAction.key_codes()
forAction::Sequence
because I'm not sure it's necessary. I have a little commented out line in place where it'd go.Overall it wasn't too bad and I learned a ton. Thanks for the assistance and let me know what I have to change or if it's all bad or whatever =)