-
Notifications
You must be signed in to change notification settings - Fork 930
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
Fix X11 KeyboardInput events sending with ibus input method. #2182
Conversation
Previously, KeyboardInput events were sent when they weren't filtered by the IM. In cases where the IM first filtered an event and later replayed it, the replayed event was the one to be sent. This caused issues under the ibus input method and repeated keys. Ibus replays the repeated key events one by one, which depending on frame rate, can be long after the key was released. The solution is to keep track of the time of arrival of the last event of each keycode. An event is a repeat if its time is not later than the previous event for the same keycode. This solution is in use in glfw as well: glfw/glfw@9a3664b
To be honest, I don't completely understand what the original issue was. Can you tell us how to reproduce this? Also please describe the expected list of KeyboardInput and ReceivedCharacter events and the actual events you get when reproducing the issue. |
@ArturKovacs you can see the effects in this bug report in Veloren: https://gitlab.com/veloren/veloren/-/issues/877 in particular this comment: https://gitlab.com/veloren/veloren/-/issues/877#note_476757314
|
Sorry for pinging, but is there a way I could assist in reviewing this PR? |
I think the issue is that we lack people who are both experienced with and interested in maintaining the X11 backend. |
The perils of open source... |
Reviewing some of the outstanding PRs / issues for x11 I also couldn't help but notice this mammoth series which includes full integration of XInput2 and libxkbcommon (definitely a good direction for the x11 backend) as well as a full port to xcb. What's notable with respect to this issue though is that the above series effectively removes IME support via the XIM protocol, arguing that IMEs could be better supported either higher up in the stack, or via direct ibus support. On the one hand it doesn't seem ideal that removing XIM integration has the effect of removing at least crude IME support from the x11 backend, but on the other hand this issue effectively demonstrates how XIM is a poor interface for IME integration, and it looks like removing XIM integration (use of XFilterEvents) entirely would potentially be another solution for this bug? |
let signed_time = xkev.time as i64; | ||
let time_since_previous_event = | ||
signed_time.wrapping_sub(key_event_times[keycode as usize] as i64); | ||
|
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 look suspicious - casting the new time and the time that it's being compared with into i64 values will defeat the purpose of using wrapping_sub to account for the 32bit timestamp wrapping around I think?
// An event is a replay of a previous event if it: | ||
// 1) Isn't filtered by the IM. | ||
// 2) Isn't the first time that an event with this keycode arrives. | ||
// 3) Has a timestamp earlier or equal to the latest non-replayed event with this 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.
could maybe also be good for future readers to have some comments about why it's necessary to filter out these repeat events. This is kind of a hacky workaround really, so it feels like that should be really clearly sign posted in the code.
&& time_since_previous_event <= 0; | ||
|
||
if !replayed_event { | ||
key_event_times[keycode as usize] = xkev.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.
considering the wrapping_sub
arithmetic earlier, I guess this should be more careful to preserve the 32bit size of the X event timestamp. I guess ffi::Time
is the same a time_t
which I think can be 8 bytes on 64bit platforms. Could be cast back again later but still, just seems like it's important to be clear about the size if using wrapping arithmetic.
At least in terms of the description of the issue given, and the proposed fix this seems like a reasonable workaround. I mean it's kinda terrible to have this kind of arms race with the XIM filtering, but x11 is kinda long in the tooth, and it's not surprising :) I suppose a question to ask here though is who's responsible for key repeat synthesis. Does winit document that it does key repeat synthesis, or set the expectation that it's left to downstream users to handle key repeat? Looking around, it seems like this is still a bit of an unanswered question: see also #310 and PistonDevelopers/piston#1220 I'm wondering about that because arguably the input module is configured by the user to generate key repeats, and the behaviour seen looks like it's technically inline with what the user had configured (except very laggy), even though that's not what was expected / desired within Veloren. Looking at the patch itself, it looks ok to me besides some questions around the wrapping arithmetic and maybe extra comments about why the filtering is needed. It would be slightly easier to review the changes if Besides the question around how winit is supposed to handle key repeats generally, I do wonder about the other (simpler) option raised by the patch series I linked above, which is that winit could drop it's use of XFilterEvents entirely and consider that mechanism to be out-dated and inadequate? |
Is this still relevant, given that #2243 has been merged? @kchibisov might be able to help in that case. |
@ArturKovacs it looks like that work was essentially to integrate more deeply with XIM so that pre-edit events from the XIM server could be translated into platform-independent Winit events. |
On my local repo, I merged master into this, but after the merge there was a (I tested on Ubuntu 20.04 LTS with GNOME 3.36.8) @kchibisov please help, this defeated me. |
@ArturKovacs I'll try to take a look once I have time for that particular issue. |
I'm facing a similar issue on macOS in Bevy when key repeat rate is set faster than FPS (15ms key repeat, 16.7ms fps), see bevyengine/bevy#5909 |
Please open a new issue for that, it'll get lost in this one |
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.
Thanks for opening this PR, I'm really excited to see progress made on it: we're had quite a number of users (including myself) bitten by this problem, and it seems like recent changes to the GNOME stack make it much more likely to happen. For these users, this isn't just a minor inconvenience: it makes playing Veloren mutually exclusive with the use of repeat keys, and most afflicted users never even discover that disabling repeat keys 'solves' the problem.
Given that this solution has precedence in GLFW, I feel like this PR is a relatively safe solution, but I've not interacted with the internals of winit much so I realise my perspective has little weight.
@@ -37,6 +37,7 @@ pub(super) struct EventProcessor<T: 'static> { | |||
pub(super) first_touch: Option<u64>, | |||
// Currently focused window belonging to this process | |||
pub(super) active_window: Option<ffi::Window>, | |||
pub(super) key_event_times: [ffi::Time; 256], |
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.
Perhaps 256 - KEYCODE_OFFSET
given that we know the last 8 entries will never be used?
if state == Pressed && !filtered_by_im { | ||
let written = if let Some(ic) = wt.ime.borrow().get_context(window) { | ||
wt.xconn.lookup_utf8(ic, xkev) | ||
} else { | ||
return; | ||
}; | ||
|
||
for chr in written.chars() { | ||
let event = Event::WindowEvent { | ||
window_id, | ||
event: WindowEvent::ReceivedCharacter(chr), | ||
}; | ||
callback(event); | ||
} | ||
} |
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 early return feels a little easy to break in a future refactor. Perhaps something a bit less brittle like the following?
if state == Pressed && !filtered_by_im {
if let Some(ic) = wt.ime.borrow().get_context(window) {
let written = wt.xconn.lookup_utf8(ic, xkev);
for chr in written.chars() {
let event = Event::WindowEvent {
window_id,
event: WindowEvent::ReceivedCharacter(chr),
};
callback(event);
}
}
}
@zesterer thanks! |
Be aware that keyboard patches won't be applied due to #2662 , so you should test on that branch your issues instead. |
Do you know whether that branch is likely to fix this problem incidentally, or will this fix likely need porting over? |
That branch has entirely different input model, so it's hard to say for me. |
Could this issue be rechecked with the latest master, given that all the keyboard input was reworked on X11 to use xkb? |
CHANGELOG.md
if knowledge of this change could be valuable to usersPreviously, KeyboardInput events were sent when they weren't filtered by
the IM. In cases where the IM first filtered an event and later replayed
it, the replayed event was the one to be sent. This caused issues under
the ibus input method and repeated keys. Ibus replays the repeated key
events one by one, which depending on frame rate, can be long after the
key was released.
The solution is to keep track of the time of arrival of the last event
of each keycode. An event is a repeat if its time is not later than the
previous event for the same keycode.
This solution is in use in glfw as well: glfw/glfw@9a3664b