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

Fix X11 KeyboardInput events sending with ibus input method. #2182

Closed
wants to merge 2 commits into from
Closed

Fix X11 KeyboardInput events sending with ibus input method. #2182

wants to merge 2 commits into from

Conversation

ashdnazg
Copy link

@ashdnazg ashdnazg commented Feb 4, 2022

  • Tested on all platforms changed
  • Added an entry to CHANGELOG.md if knowledge of this change could be valuable to users
  • Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
  • Created or updated an example program if it would help users understand this functionality
  • Updated feature matrix, if new features were added or implemented

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

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
@ArturKovacs
Copy link
Contributor

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.

@ashdnazg
Copy link
Author

@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
You can easily reproduce this on linux/X11:

  1. Set your X11 input method to ibus export XMODIFIERS=@im=ibus
  2. Turn Repeat Keys on
  3. Run the window example from this branch: https://github.com/Imberflur/winit/tree/event-test
  4. Hold any key down for 2 seconds
  5. Release the key
  6. See how you keep getting key presses.

@maroider maroider added the C - waiting on maintainer A maintainer must review this code label Feb 25, 2022
@ashdnazg
Copy link
Author

Sorry for pinging, but is there a way I could assist in reviewing this PR?

@maroider
Copy link
Member

I think the issue is that we lack people who are both experienced with and interested in maintaining the X11 backend.

@ashdnazg
Copy link
Author

The perils of open source...

@rib
Copy link
Contributor

rib commented May 29, 2022

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);

Copy link
Contributor

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.
Copy link
Contributor

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;
Copy link
Contributor

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.

@rib
Copy link
Contributor

rib commented May 29, 2022

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:
#753
so winit doesn't currently seem to have a clear opinion on how key repeats are exposed to apps, and doesn't promise that key repeats aren't delivered by the OS.

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 handle_key_events were split into a function in a separate patch, since there's a decent amount of code that moves in the patch with inline changes too, but I think I followed it. Essentially it uses a heuristic to recognise key repeats that come from an IM filter (based on their timestamp) and discards them - based on the solution in glfw.

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?

@rib rib mentioned this pull request May 29, 2022
@ArturKovacs
Copy link
Contributor

Is this still relevant, given that #2243 has been merged? @kchibisov might be able to help in that case.

@rib
Copy link
Contributor

rib commented May 30, 2022

@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.

@ArturKovacs
Copy link
Contributor

ArturKovacs commented Jun 4, 2022

On my local repo, I merged master into this, but after the merge there was a WindowEvent::KeyboardInput sent for the first IME input (this shouldn't happen). I'm guessing that ImeEvent::Start is received too late from ime_event_receiver, but I don't know why that would happen or what's the right way to fix it.

(I tested on Ubuntu 20.04 LTS with GNOME 3.36.8)

@kchibisov please help, this defeated me.

@kchibisov
Copy link
Member

@ArturKovacs I'll try to take a look once I have time for that particular issue.

@geirsagberg
Copy link

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

@madsmtm
Copy link
Member

madsmtm commented Sep 8, 2022

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

Copy link

@zesterer zesterer left a 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],

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?

Comment on lines +1276 to +1290
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);
}
}

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);
        }
    }
}

@ashdnazg
Copy link
Author

@zesterer thanks!
I'll have time to address your comments in a few days.

@kchibisov
Copy link
Member

Be aware that keyboard patches won't be applied due to #2662 , so you should test on that branch your issues instead.

@zesterer
Copy link

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?

@kchibisov
Copy link
Member

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.

@kchibisov
Copy link
Member

Could this issue be rechecked with the latest master, given that all the keyboard input was reworked on X11 to use xkb?

@kchibisov kchibisov closed this Oct 20, 2023
@kchibisov kchibisov removed the C - waiting on maintainer A maintainer must review this code label Oct 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

Successfully merging this pull request may close these issues.

8 participants