-
Notifications
You must be signed in to change notification settings - Fork 78
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
Add key repetition #16
Conversation
Doesn't build on 1.20.0 but I plan to change the way repeatable characters are chosen anyway |
Alacrity works but only displays the characters as you hold down the key if you also simultaneously hovering your mouse over the buttons which also means that the hover colors are properly displayed as well. Very strange! Maybe alacrity, glutin or winit suppress window drawing which would explain the buttons? or maybe a bug. |
Hmm, I'm a little uneasy about integrating it like that. While I agree it would be good to integrate key repetitions, I feel like implementing it using threads is pretty eavy, especially if it means spawning a new thread every key press. Also, what you encounter here:
Is likely because the winit event loop only waits on events from the wayland connection, and not on events from your repetition thread. As such, if you don't move the pointer, no wayland events are generated, and the event loop remains stuck. Because of this, I don't think it'll be possible to implement key repetition in a way that "just works", without special integration in the event loop (by making it possible to wait on more event sources than just the wayland socket), and thus cooperation from the downstream user. |
I also think the current implementation is crude at present but I am unsure what you mean by not using threads, I think to do something like this you need to use threads either directly or indirectly. As for the threads spawning every key press I can make it so only one thread exist for the key repetition and thread sleeping can be replaced by tick from the chan crate. That being said if you want it done without threads directly I can use something like tokio. 👍 |
In all cases, independently of the actual way it is implemented:
pub KeyRepeatKind {
None, // no key repeat
Fixed { rate: u64, delay: u64 }, // use a fixed rate/delay configuration
System, // use the rate/delay configuration provided by the server
}
That said, sorry I had glanced too quickly at your code yesterday, I see you are directly calling the callbacks from the secondary thread. By my other message still explains your weird bug though: winit actually buffers the events generated in the I'm not completely clear about what would be the best way to tackle this cleanly, though. |
I have made the chan crate handle the timing and interaction between the main and secondary thread, this should now mean the secondary thread is immediately closed when not needed. |
Hopefully this should be pretty near complete. I have a question about the time on the event, would it be better to use rate and delay to increment the time member on the key event when it's sent or set it to none or something, I ask because I'm not sure how time is calculated or how its used. |
@trimental Just want to mention that chan crate seems to be EOL and it is replaced by https://github.com/crossbeam-rs/crossbeam-channel. See last comment here BurntSushi/chan#25 |
@heghe crossbeam would be great to replace chan here :) but unfortunately it’s minimum supported rust version is 1.26 and this project has a minimum of 1.20. Technically this is a breaking change pull request so the minimum supported version could be bumped but I assume @vberger wouldn’t want to do that. |
Mm I’m starting to question if the chan/crossbeam crates are either A - important for function or B - performance efficient when compared to the original thread sleep way. With respect to A it should function the same if a thread kill check is done at least before the sending of an event and with respect to B it should be just as fast if not faster maybe. I think the chan crate might just be adding a nice front end to the original way. |
Looks like it's tempfile that is actually messing up the build process, they upgraded rand to 5 and released it as a non-major release. The fix would be bumping the minimum supported rust version to 1.22, downgrading tempfile or removing tempfile. Regardless of this I would still prefer to not use the chan crate, I don't think it provides a lot for this usage scenario. |
…e unused AsciiExt
Re-reading your code, I think it could use some refactoring:
|
The commit bf3056e changes the behavior of key repetition to this
Due to these guarantees, if you ignore key repeat events then map_keyboard_with_repeat will essentially be map_keyboard. Press events of key A will never occur while repeating key B, release of events of key A may occur when repeating key B. I think this is the best behavior for key repetition |
I think I need some more clarification on
do you mean a alternative to kbd.implement() like repeat_kbd.implement()? |
Hmm, I'm not sure I understand what you mean here. I'll put an example of what I think is the expected behavior:
Do we agree on this stream of events?
I mean, when implementing the impl Implementation<Proxy<WlKeyboard>, wl_keyboard::Event> for KbdImplementation {
fn receive(&mut self, event: wl_keyboard::Event, kbd: Proxy<WlKeyboard>) {
/* contents of the big closure */
}
} This would allow to more easily factor code between the two implementations, and possibly split the whole logic into a few methods of |
I'm sorry but I'm not that use to the Implement trait, I got something like this
does KbdClosure need to take user_impl and repeat_impl? |
I might have to complete this pull request without this and leave you to implement it later if I can't figure it out from your explanations. |
Unless I'm mistaken you could also maybe have this
map_keyboard would pass none and map_keyboard_with_repeat would pass values. Then it would be as simple as a if let in the Key Press arm of the event match. Your way might be better though as I don't fully understand it yet. |
The idea of the 'Implementation' trait is more or less the concretisation of the fact that a closure if really just a struct that happens to be callable.
As such, if you want to transform a closure into a struct, you indeed have to create a struct whose fields are exactly the variables that the closure captured. Then, in the implementation, just use the same code as what the closure had, replacing the captured variable by access to the fields.
When doing this kind of migration, I just tend to move the code from the closure to the impl block then follow the compiler error messages until it compiles. ^^"
As an example, you can see some direct implementations in smithay, here for example: https://github.com/Smithay/smithay/blob/master/src/wayland/shm/mod.rs
|
Indeed, having an Option argument would work too, if you prefer that way. :)
|
When I did
I was left with a final error message saying this for the Impl as well as RepeatImpl
I'll write it as a Option for now but you can change it to a struct Implementation if you can get it working |
That should be almost everything apart from one last thing. At the moment the behavior is like this
Note that although the key changes from v to V, neither the release event for v, nor the press event for V is sent to the user. I want to know whether you think this is the best behavior or whether to send a release for v and a press for V when shift is pressed. |
Also I forgot to answer
Yes that should be the behavior |
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.
Okay, this is pretty good, I've left a few points, most are me being nitpicky.
Sorry for the long back and forth on this PR, thanks for enduring me this whole time 😅
src/keyboard/mod.rs
Outdated
pub keysym: u32, | ||
/// utf8 interpretation of the entered text | ||
/// | ||
/// will always be `None` on key release events |
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 second line of this comment is no longer relevant I think
src/keyboard/mod.rs
Outdated
state: Arc<Mutex<KbState>>, | ||
key_repeat_kind: Option<KeyRepeatKind>, | ||
mut event_impl: Impl, | ||
repeat_impl: Option<Arc<Mutex<Implementation<Proxy<wl_keyboard::WlKeyboard>, KeyRepeatEvent> + Send>>>, |
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 think it'd be better if the wrapping in Arc<Mutex<...>>
was done inside of this function, as the outside world never need to access them anyway.
So, I'd rather see its prototype as:
fn implement_kbd<Impl, RepeatImpl>(
kbd: NewProxy<wl_keyboard::WlKeyboard>,
state: KbState,
event_impl: Impl,
repeat: Option<(KeyRepeatKind, RepeatImpl)>
) -> Proxy<wl_keyboard::WlKeyboard>
where:
for<'a> Impl: Implementation<Proxy<wl_keyboard::WlKeyboard>, Event<'a>> + Send,
RepeatImpl: Implementation<Proxy<wl_keyboard::WlKeyboard>, KeyRepeatEvent> + Send
{
/* ... */
}
src/keyboard/mod.rs
Outdated
kbd.implement( | ||
move |event: wl_keyboard::Event, proxy: Proxy<wl_keyboard::WlKeyboard>| { | ||
match event { | ||
wl_keyboard::Event::Keymap { format, fd, size } => { | ||
let mut state = state.lock().unwrap(); |
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 state should be locked once and for all before the match, rather than once in each arm.
src/keyboard/mod.rs
Outdated
); | ||
if let Some(repeat_impl) = repeat_impl.clone() { | ||
// Check with xkb if key is repeatable | ||
if unsafe { (XKBH.xkb_keymap_key_repeats)(state.lock().unwrap().xkb_keymap, key + 8) == 1 } { |
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 think the "check if a key is repeatable" functionality should rather be a method of KbState
, to contain all FFI at the same place.
src/keyboard/mod.rs
Outdated
thread_sym = thread_state.lock().unwrap().get_one_sym_raw(key); | ||
thread_utf8 = { | ||
let mut thread_state = thread_state.lock().unwrap(); | ||
if thread_state.compose_feed(sym) |
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 don't think we should check compose on key repeat like this. Compose does not really make sense for key repetition...
And on my computer, if I do ^
then e
(which compose into ê
) and let e
pressed, I get êeeeeeeeeeeee
. Only the first letter gets composed.
So, I'd say this whole arm of the match should be simplified to
let mut thread_state = thread_state.lock().unwrap();
thread_sym = thread_state.get_one_sym_raw(key);
thread_utf8 = thread_state.get_utf8_raw(key);
thread_modifiers = thread_state.mods_state.clone();
src/keyboard/mod.rs
Outdated
} => state.update_modifiers(mods_depressed, mods_latched, mods_locked, group), | ||
} => { | ||
state.lock().unwrap().update_modifiers(mods_depressed, mods_latched, mods_locked, group); | ||
state_chan.lock().unwrap().0.send(()).unwrap(); |
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.
Doesn't that stop any current repetition if one was going on?
Pressing a modifier should change the repeated key but not stop the repetition.
Also, what's the impact of sending a message in this channel if no repetition is going on?
/// as such you need to call this method as soon as you have created the keyboard | ||
/// to make sure this event does not get lost. | ||
/// | ||
/// Returns an error if xkbcommon could not be initialized. |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
); | ||
if let Some(repeat_impl) = repeat_impl.clone() { | ||
// Check with xkb if key is repeatable | ||
if unsafe { state.key_repeats(key + 8) } { |
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 + 8
offsetting should happen inside of the key_repeats
method, like for the other methods of KbState
.
state.update_modifiers(mods_depressed, mods_latched, mods_locked, group); | ||
if key_held.is_some() { | ||
state_chan.lock().unwrap().0.send(()).unwrap(); | ||
} |
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 code still means that pressing a modifier will stop any repeat, right? I don't think that's the expected behavior.
If I hold a
on my computer and press then release <shift>
several times, I get something like aaaaaaaaaaaaaAAAAAAAAAAaaaaaaaaaaAAAAAAaaaaaaaaaAAAAAaaaaaaaa
.
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.
Maybe you are thinking of kill_chan instead of state_chan, state_chan simply requests the thread to update modifiers when they are changed rather then updating the values every 30ms. I don't really know whether this is much more performant then just updating them for every key repetition though.
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.
Actually re-reading that, is
aaaaaaaaaaaaaAAAAAAAAAAaaaaaaaaaaAAAAAAaaaaaaaaaAAAAAaaaaaaaa
not the expected behavior? what would be?
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.
Maybe you are thinking of kill_chan instead of state_chan
Ah right, my bad. I've read it too quickly. No problem then.
I've tried to implement all of your review points, the only one I had difficulty with was
as I can't move RepeatImpl out of a container as it needs a size, unless I move it to generics, which I can't do because then all calls must provide a RepeatImpl object, when some pass None. (atleast I'm pretty sure thats why)
Not at all, I love learning more about the wayland protocol and your reviews have been spot on, its best to get these things as stable as possible :) |
Ah yes, I've seen this problem several times. Rustc can't infer the type parameter of |
Works beautifully, the parameters and their calling looking a lot better then before |
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.
Alright, this looks good now 👍 I'll just let the travis build finish before merging it.
I still think the implementation needs to be refactored into a stand-alone struct, this closure is getting far too big for its own right. But this can be done in a new PR, at some point.
Thanks!
This pull request adds key repetition as a possible utility for the keyboard module. The work isn't there yet for release as there will at least need to be a setting for enabling/disabling it and bitflags to control what keys are repeatable. I'm pull requesting to see your views on the key repetition and whether it would be suitable for the client toolkit. I saw the pull request for winit but that looked pretty stale and I think it would fit better as an option in the client toolkit then in winit. I used threads, channels and sleeping as opposed to something like tokio as its simpler and lighter, the only downside I can think of is that threads might last a few hundred milliseconds longer due to sleeping (which I'm not sure whether that matters?) however cpu wise it should be pretty light. I don't know how releases work but maybe it would be a minor breakage if it was defaulted to off?