Skip to content

GTK: native text controls swallow the first keystroke(s) on focus when a key event is handled #2970

Description

@Descolada

Summary

On the GTK backend, a TextBox/TextArea/PasswordBox silently drops the initial keystrokes the first time it is focused if a KeyDown (or KeyUp/TextInput? haven't tested those) handler is attached to it. The KeyDown event still fires for those keys, but no text is inserted and TextChanged does not fire. A text control with no key handler is unaffected.

Input sometimes starts working after focusing away and back, but this recovery is inconsistent.

Environment

  • OS: Linux (X11)
  • Backend: Eto.Platform.Gtk (GTK3)
  • GtkSharp: 3.24.24.95
  • Runtime: .NET 10
  • Input method: ibus active (GTK_IM_MODULE=ibus, XMODIFIERS=@im=ibus)

Steps to reproduce

In the test gallery add one line to LogEvents in test/Eto.Test/Sections/Controls/TextAreaSection.cs, then run Controls → TextArea and type on first focus:

control.KeyDown += (sender, e) => Log.Write(control, "KeyDown: {0}", e.Key);

Expected

Typing into the focused control inserts the characters, regardless of whether a KeyDown handler is attached.

Actual

On first focus the initial keystroke(s) are silently dropped — KeyDown fires but nothing is inserted and TextChanged does not fire. Removing the key handler makes typing work normally, which isolates the trigger.

Confirmed affected: TextBox (Gtk.Entry) and TextArea (Gtk.TextView); should apply to anything routed through GtkControl (e.g. PasswordBox).

Root cause

The shared IM context in src/Eto.Gtk/Forms/GtkControl.cs was changed from Gtk.IMContextSimple to Gtk.IMMulticontext (in the change that added the Drawable TextComposition/TextInsertionBoundsRequested APIs):

context = new Gtk.IMMulticontext();

HandleKeyPressEvent runs that IM context's FilterKeypress for every control that has a key handler attached (the [GLib.ConnectBefore] handler is wired up whenever KeyDownEvent/TextInputEvent is attached):

if (e == null || !e.Handled)
{
    commitHandled = false;
    UpdateDrawableInputMethodLocation(handler);
    if (Context.FilterKeypress(args.Event))   // runs for native Entry/TextView too
        args.RetVal = commitHandled;
}

For a native Gtk.Entry/Gtk.TextView, this means two IM contexts process the same key events: Eto's IMMulticontext (which delegates to the system IME, ibus here) and the widget's own built-in one. With the old IMContextSimple this was harmless — it returns false from FilterKeypress for ordinary characters, so the native widget handled them. IMMulticontext instead consumes them via the system IME and commits them through Eto's Commit handler (which only raises TextInput — it does not insert into the native widget), so the native widget never receives the input.

It manifests on first focus because the IM context is only focus-managed (FocusIn/FocusOut) when shouldFocusInputContext is true — i.e. when TextInput is handled or it's a Drawable composition — yet FilterKeypress is called regardless. So for a plain native text control the context filters keys while never being properly focused.

Suggested fix

Gate the IM-context key filtering on the same condition that already gates its focus management, so Eto's IM context is only engaged for controls that actually consume it (custom TextInput handlers or Drawable composition). Native text widgets then fall through to their own IM, as before the regression:

bool ShouldUseInputContext(GtkControl<TControl, TWidget, TCallback> handler)
    => handler.IsEventHandled(Eto.Forms.Control.TextInputEvent) || HandlesDrawableComposition(handler);
// HandleKeyPressEvent
if ((e == null || !e.Handled) && ShouldUseInputContext(handler))
{
    commitHandled = false;
    UpdateDrawableInputMethodLocation(handler);
    if (Context.FilterKeypress(args.Event))
        args.RetVal = commitHandled;
}

(and use the same ShouldUseInputContext for the existing shouldFocusInputContext check in FocusInEvent). This keeps the new Drawable composition feature intact while restoring first-focus input on native text controls.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions