Skip to content

X11/Wayland: Fl_Text_Editor's shortcuts and Fl::event_key don't work as expected with certain keyboard layout settings #1427

Open

Description

@savannstm

Description of the Bug Report
Standard shortcuts (Ctrl+Z, Ctrl+C, etc.) in Fl_Text_Editor stop working when a non-Latin keyboard layout is set as the primary layout in the system.

Snippet from Fl_Text_Editor:

// These are the default key bindings every widget should start with
static struct {
  int key;
  int state;
  Fl_Text_Editor::Key_Func func;
} default_key_bindings[] = {
    ...
    { 'z',          FL_CTRL,                  Fl_Text_Editor::kf_undo       },
    { 'z',          FL_CTRL|FL_SHIFT,         Fl_Text_Editor::kf_redo       }, // Windows screen driver also defines Ctrl-Y
    { '/',          FL_CTRL,                  Fl_Text_Editor::kf_undo       }, // Emacs
    { '?',          FL_CTRL,                  Fl_Text_Editor::kf_redo       }, // Emacs
    { 'x',          FL_CTRL,                  Fl_Text_Editor::kf_cut        },
    { FL_Delete,    FL_SHIFT,                 Fl_Text_Editor::kf_cut        },
    { 'c',          FL_CTRL,                  Fl_Text_Editor::kf_copy       },
    { FL_Insert,    FL_CTRL,                  Fl_Text_Editor::kf_copy       },
    { 'v',          FL_CTRL,                  Fl_Text_Editor::kf_paste      },
    { FL_Insert,    FL_SHIFT,                 Fl_Text_Editor::kf_paste      },
    { 'a',          FL_CTRL,                  Fl_Text_Editor::kf_select_all },
    { 0,            0,                        0                             }
};

At Fl_Text_Editor::handle_key, FLTK just checks whether Fl::event_key is in the list, using Fl_Text_Editor::bound_key_function function:

Fl_Text_Editor::Key_Func Fl_Text_Editor::bound_key_function(int key, int state, Key_Binding* list) const {
    Key_Binding* cur;
    for (cur = list; cur; cur = cur->next)
        if (cur->key == key)
        if (cur->state == FL_TEXT_EDITOR_ANY_STATE || cur->state == state)
            break;
    if (!cur) return 0;
    return cur->function;
}

The problem is that the key code, returned by Fl::event_key depends on set system layouts.

Desktop environments allow to add multiple keyboard layout, and in KDE Plasma this is a list of layouts, where layouts can be reordered. The first layout from the list is used to match the pressed physical key and return a code for it. When Russian layout is at the top, pressing physical 'a' key on the keyboard will cause Fl::event_key to return 0x06c6, which corresponds to XK_Cyrillic_ef from X11's keysymdef.h:

#define XK_Cyrillic_ef                   0x06c6  /* U+0444 CYRILLIC SMALL LETTER EF */

This problem doesn't happen if English layout (or any Latin layout, really) is at the top of the list. Then, the returned key is a proper ASCII value for 'a'.

It is not stated clearly in the docs that something like that is possible and I personally assumed (although I don't know, whether everyone would assume) that Fl::event_key should correspond to a physical key/ASCII value, not some layout-dependent value.

Returned keysym can be converted into a keycode and then back to ASCII keysym by iterating over groups and levels in a specifically created Latin keymap, but that's just wasteful.

Instead, it looks like Fl::event_key should return a keycode instead of keysym that corresponds to a concrete key. For example, it should return FL_A constant for physical 'a' key. This can be somewhat achieved right now under X11 by using X11 intrinsics directly which will return a direct keycode that can later by converted like this:

// inside handle method
int key = Fl::event_key();
KeyCode keycode = ((XKeyEvent*)fl_xevent)->keycode;
key = (int)XkbKeycodeToKeysym(fl_display, keycode, group, 0);

where group is the index of Latin group in layout settings - in my case, this is 1 when Russian layout is at the top. Finding the right Latin group index is another challenge - and it shouldn't be done in the first place since it's a roundtrip back to XKB. It makes no sense to convert Fl::event_key's keysym to keycode by manual seeking, and then AGAIN from keycode to ASCII keysym. FLTK receives full XKB data in the first place, so why not expose it to the user?

Currently, the issue roots from Fl_x.cxx, fl_handle function under case KeyRelease case. It specifically assigns Fl::e_keysym, that Fl::event_key uses to return the keysym to the caller, as expected from the e_keysym name. The problem with that is that it does not align with the current Fl_Text_Editor implementation.

To fix this, a new function can be added to Fl namespace, let's say Fl::event_keycode. This would return a value from new Fl::e_keycode static (that contains raw XKB keycode int), and this static's value in turn will be assigned from the very same fl_handle function that assigns Fl::e_keysym. Then, without breaking the ABI, Fl::event_key usage can be substituted by Fl::event_keycode in Fl_Text_Editor, instantly fixing all possible errors with different layouts.

Also, another set of contants may be added for event_keycode, that match the XKB keycodes, e.g.

enum {
    FL_KEYCODE_A = 38, // On Linux, stable value is 38, because evdev's value for 'a' key is 30. Since 0-7 values are reserved in XKB, it adds 8 to the evdev's value. Look into linux/input-event-codes.h
}

There's another way to propagate the right physical key to the user: convert the keycode to ASCII KeySym using specifically created Latin locale:

#include <X11/Xlib.h>
#include <X11/XKBlib.h>

KeySym keycode_to_latin_keysym(Display *dpy, int keycode)
{
    static XkbDescPtr latin_map = NULL;

    if (!dpy) {
        if (latin_map) {
            XkbFreeKeyboard(latin_map, XkbAllComponentsMask, True);
            latin_map = NULL;
        }
        return NoSymbol;
    }

    if (!latin_map) {
        XkbComponentNamesRec comp = {
            .keycodes = "evdev",
            .types    = "complete",
            .compat   = "complete",
            .symbols  = "pc+us",
            .geometry = "pc(pc105)",
        };
        latin_map = XkbGetKeyboardByName(dpy, XkbUseCoreKbd, &comp,
                                         XkbGBN_AllComponentsMask,
                                         XkbGBN_AllComponentsMask,
                                         False);
        if (!latin_map)
            return NoSymbol;
    }

    if (keycode < latin_map->min_key_code || keycode > latin_map->max_key_code)
        return NoSymbol;

    return XkbKeySymEntry(latin_map, (KeyCode)keycode, 0, 0);
}

This approach just converts the X11/XKB keycode into an ASCII keysym. It can be matched against raw char literals.

Personally, I think both e_keycode and keycode-to-latin-keysym approaches will work. Keycode to latin keysym might be better, because it doesn't require user to handle XKB intrinsics themselves while dealing with keycode. FLTK just returns the Latin keysym right away, and no matter which keyboard layout is active now - physical key corresponding to ASCII 'a' will return ASCII 'a'. Furthermore, this approach may replace the current e_keysym implementation right away, because, factually, it fixes more, than breaks.

I'm willing to try to implement this if the approach is approved. Wayland is not affected by this.

To Reproduce
Steps to reproduce the behavior:

  1. In your desktop environment's keyboard settings, add a non-Latin layout (e.g. Russian, Arabic, Greek) and move it to the top of the layout list, above any Latin layout.
  2. Open a bare minimum application with only the Fl_Text_Editor, input some text, and attempt standard shortcuts (Ctrl+Z, Ctrl+C, etc.). This is reproducible in any environment that supports layout ordering (confirmed in KDE Plasma).
  3. Go back to desktop environment's keyboard settings, move a Latin layout to the top, or entirely remove non-Latin layouts. Apply settings, go back to the application without even restarting it. Now - everything works.

Expected behavior
A clear and concise description of what you expected to happen.

Standard shortcuts should work with any layout, without depending on a specific (e.g. Latin) layout.

Screenshots
If applicable, add screenshots to help explain your problem.

N/A

FLTK Version
Please complete the following information and delete non-applicable lines:

FLTK Configure / Build Options
Please add the complete CMake and build command lines if you built FLTK yourself.
Please add all CMake options you changed by using CMake GUI tools (if any).

  • -DBUILD_SHARED_LIBS=OFF
  • -DFLTK_BUILD_SHARED_LIBS=OFF
  • -DFLTK_BACKEND_X11=ON
  • -DFLTK_BACKEND_WAYLAND=OFF
  • -DFLTK_USE_SYSTEM_ZLIB=OFF
  • -DFLTK_USE_SYSTEM_LIBPNG=OFF
  • -DFLTK_USE_SYSTEM_LIBJPEG=OFF
  • -DFLTK_BUILD_FLUID=OFF
  • -DFLTK_BUILD_FLTK_OPTIONS=OFF
  • -DFLTK_BUILD_GL=OFF
  • -DFLTK_BUILD_FORMS=OFF
  • -DFLTK_BUILD_TEST=OFF
  • -DFLTK_BUILD_HTML_DOCS=OFF
  • -DFLTK_OPTION_SVG=OFF
  • -DFLTK_OPTION_CAIRO_WINDOW=OFF
  • -DFLTK_OPTION_CAIRO_EXT=OFF
  • -DFLTK_OPTION_PEN_SUPPORT=OFF
  • -DFLTK_OPTION_LARGE_FILE=OFF
  • -DFLTK_OPTION_PRINT_SUPPORT=OFF
  • -DFLTK_OPTION_FILESYSTEM_SUPPORT=OFF
  • -DFLTK_OPTION_STD=OFF
  • -DFLTK_USE_XINERAMA=OFF
  • -DFLTK_USE_PTHREADS=OFF
  • -DFLTK_USE_XFIXES=OFF
  • -DFLTK_USE_XCURSOR=OFF
  • -DFLTK_USE_PANGO=OFF
  • -DFLTK_USE_XFT=ON
  • -DFLTK_USE_XRENDER=OFF
  • -DFLTK_USE_DBUS=OFF
  • -DFLTK_USE_POLL=OFF
  • -DFLTK_USE_LIBDECOR_GTK=OFF
  • -DFLTK_USE_SYSTEM_LIBDECOR=OFF
  • -DFLTK_GRAPHICS_CAIRO=OFF
  • -DFLTK_GRAPHICS_GDIPLUS=OFF

Operating System / Platform:
Please be as precise as possible, e.g. "Linux: Ubuntu 20.04"

  • OS: Linux
  • OS Version: CachyOS
  • Kernel: 7.0.1-1-cachyos
  • DE: KDE Plasma 6.6.4 (Wayland)

Linux/Unix Runtime, if applicable:

  • DE: KDE Plasma 6.6.4 (Wayland)
  • Application Runtime: X11/XWayland (-DFLTK_BACKEND_WAYLAND=OFF, -DFLTK_BACKEND_X11=ON)

Additional context
Add any other context about the problem here.

Also tested with FLTK 1.4.4-4.1 from cachyos-extra-v3/fltk CachyOS repository using FLTK_BACKEND=x11 flag to run in X11 mode. The same behavior.

Activity

  1. savannstm commented on May 2, 2026

    @savannstm
    Author

    A little update to not change the main issue body, and avoid confusing anyone. Wayland is affected too (I used FLTK 1.4.4-4.1 from cachyos-extra-v3/fltk with FLTK_BACKEND=wayland), but in a different way. Initially, I tested Wayland with Russian layout at the top in system layouts, but with the Latin layout as the active one - that's why the issue didn't reveal itself.

    If X11 driver queries the zeroth group, which depending on layout ordering may result in non-Latin layout, Wayland driver does it differently, but it's also not correct.

    Wayland driver queries the current XKB state, inherited from the environment:

    static int process_wld_key(struct xkb_state *xkb_state, uint32_t key,
                               uint32_t *p_keycode, xkb_keysym_t *p_sym) {
      uint32_t keycode = key + 8;
      xkb_keysym_t sym = xkb_state_key_get_one_sym(xkb_state, keycode);
      if (sym == 0xfe20) sym = FL_Tab;
      if (sym == 0xffeb) sym = FL_Meta_L; // repair value libxkb gives for FL_Meta_L
      if (sym == 0xffec) sym = FL_Meta_R; // repair value libxkb gives for FL_Meta_R
      if (sym >= 'A' && sym <= 'Z') sym += 32; // replace uppercase by lowercase letter
      int for_key_vector = sym; // for support of Fl::event_key(int)
      // special processing for number keys == keycodes 10-19 :
      if (keycode >= 10 && keycode <= 18) {
        for_key_vector = '1' + (keycode - 10);
      } else if (keycode == 19) {
        for_key_vector = '0';
      }
      if (p_keycode) *p_keycode = keycode;
      if (p_sym) *p_sym = sym;
      return for_key_vector;
    }
    
    static void wl_keyboard_key(void *data, struct wl_keyboard *wl_keyboard,
                   uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
    ...
      static char buf[128];
      uint32_t keycode;
      xkb_keysym_t sym;
      int for_key_vector = process_wld_key(seat->xkb_state, key, &keycode, &sym);
    ...
      Fl::e_keysym = Fl::e_original_keysym = for_key_vector;
    ...
    }

    This state is based not on the current system layout order, but on the currently active layout. If currently active layout is Latin, then everything is okay, shortcuts work, Fl::event_key returns ASCII values. But the moment the layout switched to non-Latin (Russian in my case) everything breaks.

    The fix is mostly the same for Wayland XKB - it's either to introduce a new public function like Fl::event_keycode or explicitly match the received keycode against programmatically created Latin layout; not the currently active system layout, and return an ASCII value.

    And I'll remind, I'm willing to implement any of the said approaches after a discussion. Because currently I'm not sure which one will be better, it's for you, maintainers, to decide.

  2. changed the title [-]X11: Fl_Text_Editor's shortcuts and Fl::event_key don't work as expected with certain keyboard layout settings[/-] [+]X11/Wayland: Fl_Text_Editor's shortcuts and Fl::event_key don't work as expected with certain keyboard layout settings[/+] on May 2, 2026
  3. savannstm commented on May 4, 2026

    @savannstm
    Author

    I inspected the Windows driver and found out everything works on Windows, because Fl::e_keysym in this scenario is assigned from this call: ms2fltk(wParam, lParam & (1 << 24)), where ms2fltk initializes a table of ASCII characters and finds the required character from wParam argument, effectively always returning an ASCII character.

    I implemented a drop-in both X11 and Wayland fix, which returns proper ASCII equivalents for pressed keys, when those keys correspond to letters at savannstm@eca313a. It's better than anything I suggested above because it accounts for key order in layout and doesn't require creating any "sample" Latin layout and maintaining it, it basically works with any XKB system with any layouts correctly.

    I'm happy to open a PR if this commit is acceptable.

  4. self-assigned this
    on May 4, 2026
  5. ManoloFLTK commented on May 4, 2026

    @ManoloFLTK
    Contributor

    @savannstm Many thanks for this important issue report and for your suggestions about how to fix it. I have looked in detail at your proposal for a fix. Unfortunately, it doesn't work correctly for latin but not english-like keyboard layouts (e.g. the French layout where some latin letters are positioned differently than in the English layout). I have committed fixes for the X11, Wayland and macOS platforms. Could you test them under X11 and Wayland and report here if they work correctly in your hands? If so, I will backport them to the branch-1.4 branch.

  6. Albrecht-S commented on May 4, 2026

    @Albrecht-S
    Member

    @ManoloFLTK No, No, No! PLEASE DO NOT BACKPORT THIS to 1.4 !!!.

    Please let's discuss this first in private mail(s) with Matthias and me.

  7. savannstm commented on May 4, 2026

    @savannstm
    Author

    @ManoloFLTK I can confirm everything works with master branch both under X11 and Wayland. Also, if you are talking about that commit in my fork that I'm referring to, I queried the key name, but hardcoded QWERTY assumption, I realize I screwed up here. I thought there's some way to get ASCII character for key while still accouting for the layout and the key order in the layout, looks like it's not the case. Anyway, your fix has no issues with me. Close the issue whenever you want.

  8. 29 remaining items

  9. Albrecht-S commented on May 9, 2026

    @Albrecht-S
    Member

    FTR: I found a very old but interesting article on ux.stackexchange.com. Just saving the URL here so it isn't lost, and other readers may view it.

    Citation:

    Generally, the user does not need to manually switch to a QWERTY, AZERTY or other Latin keyboard layout in order to use keyboard shortcuts that use a modifier like ctrl or alt. Once you hold down the modifier like ctrl, you can press the Z as it would be found in a Latin keyboard layout. I've noticed this difference in operating systems, though:

    On Windows, with an Arabic keyboard layout enabled, once you hold down a modifier like ctrl, you need to press the subsequent letter key (like Z) where it would be found on a QWERTY keyboard, even if no QWERTY keyboard layout is configured. (This is annoying if you are switching between Dvorak and Arabic, as the keyboard shortcuts change)

    On Linux, with an Arabic keyboard layout enabled, once you hold down a modifier like ctrl, you need to press the subsequent letter key (like Z) where it would be found in the alternative keyboard layout. So if your alternative keyboard layout is Dvorak, it switches to that, if it is QWERTY, it switches to QWERTY.

    Conclusion

    On Linux it appears to be intentional (according to the author's observation) to switch implicitly to "the alternative keyboard layout". This seems to cause the issues we saw when more than one layout is defined, if the order of layouts is changed by the user in the setup of KDE and/or Gnome. That's certainly meant to be the case using X11 because Wayland was not in use at the time of their writing.

  10. ManoloFLTK commented on Jun 4, 2026

    @ManoloFLTK
    Contributor

    @Albrecht-S

    BTW, I can post examples of what happens with the Serbian keyboard if anybody is interested. It's really more than confusing.

    Please, post here your findings with that keyboard layout. TIA.

  11. ManoloFLTK commented on Jun 15, 2026

    @ManoloFLTK
    Contributor

    @savannstm @Albrecht-S Please, see PR #1461 intended to fix this issue and more.

  12. savannstm commented on Jun 16, 2026

    @savannstm
    Author

    @ManoloFLTK No complaints about this PR. Brilliant reasoning and observations. For me personally, everything looks more than good.

  13. added this to the FLTK 1.5.0 milestone on Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Platform: Waylandplatform specific (Unix/Wayland)Platform: X11platform specific (Unix/X11))Platform: macOSplatform specific (macOS)Prio 2 - Lowdocumentation error or undocumented side-effectactiveSomebody is working on it

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions