From 2c0f6a2aa0c8d0650d1f856aa279ce7495925c17 Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Fri, 31 Mar 2023 02:43:28 +0300 Subject: [PATCH] Fix wxKeyEvent::GetKeyCode() for non-US keyboard layouts Use the key code corresponding to the key in the US keyboard layout for the key down/up events even when not actually using US layout, as this is much more useful than simply returning 0 as was done before. It also is compatible with the behaviour of the other ports. Closes #17643. Closes #23379. See #23410. --- build/tools/before_install.sh | 2 +- configure | 96 ++++++++++++++++++++++++ configure.in | 19 +++++ setup.h.in | 3 + src/gtk/window.cpp | 133 ++++++++++++++++++++++++++++++++-- 5 files changed, 247 insertions(+), 6 deletions(-) diff --git a/build/tools/before_install.sh b/build/tools/before_install.sh index da7298dda8..dc24d22051 100755 --- a/build/tools/before_install.sh +++ b/build/tools/before_install.sh @@ -74,7 +74,7 @@ case $(uname -s) in extra_deps='libwebkit2gtk-4.0-dev libgspell-1-dev' ;; 2) libtoolkit_dev=libgtk2.0-dev - extra_deps='libwebkitgtk-dev' + extra_deps='libwebkitgtk-dev libxkbcommon-dev' ;; *) echo 'Please specify wxGTK_VERSION explicitly.' >&2 exit 1 diff --git a/configure b/configure index 3bd652daeb..b602b6ce47 100755 --- a/configure +++ b/configure @@ -946,6 +946,8 @@ GSPELL_LIBS GSPELL_CFLAGS LIBSECRET_LIBS LIBSECRET_CFLAGS +XKBCOMMON_LIBS +XKBCOMMON_CFLAGS LIBICONV CXXFLAGS_VISIBILITY CFLAGS_VISIBILITY @@ -1415,6 +1417,8 @@ WAYLAND_EGL_CFLAGS WAYLAND_EGL_LIBS MesaGL_CFLAGS MesaGL_LIBS +XKBCOMMON_CFLAGS +XKBCOMMON_LIBS LIBSECRET_CFLAGS LIBSECRET_LIBS GSPELL_CFLAGS @@ -2446,6 +2450,10 @@ Some influential environment variables: MesaGL_CFLAGS C compiler flags for MesaGL, overriding pkg-config MesaGL_LIBS linker flags for MesaGL, overriding pkg-config + XKBCOMMON_CFLAGS + C compiler flags for XKBCOMMON, overriding pkg-config + XKBCOMMON_LIBS + linker flags for XKBCOMMON, overriding pkg-config LIBSECRET_CFLAGS C compiler flags for LIBSECRET, overriding pkg-config LIBSECRET_LIBS @@ -35834,6 +35842,94 @@ $as_echo "$as_me: WARNING: wxFileSystemWatcher won't be available on this platfo fi fi +if test "$wxUSE_GTK" = 1; then + if test "$USE_WIN32" != 1 -a "$USE_DARWIN" != 1; then + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for XKBCOMMON" >&5 +$as_echo_n "checking for XKBCOMMON... " >&6; } + +if test -n "$PKG_CONFIG"; then + if test -n "$XKBCOMMON_CFLAGS"; then + pkg_cv_XKBCOMMON_CFLAGS="$XKBCOMMON_CFLAGS" + else + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"xkbcommon\""; } >&5 + ($PKG_CONFIG --exists --print-errors "xkbcommon") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_XKBCOMMON_CFLAGS=`$PKG_CONFIG --cflags "xkbcommon" 2>/dev/null` +else + pkg_failed=yes +fi + fi +else + pkg_failed=untried +fi +if test -n "$PKG_CONFIG"; then + if test -n "$XKBCOMMON_LIBS"; then + pkg_cv_XKBCOMMON_LIBS="$XKBCOMMON_LIBS" + else + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"xkbcommon\""; } >&5 + ($PKG_CONFIG --exists --print-errors "xkbcommon") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_XKBCOMMON_LIBS=`$PKG_CONFIG --libs "xkbcommon" 2>/dev/null` +else + pkg_failed=yes +fi + fi +else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + XKBCOMMON_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "xkbcommon"` + else + XKBCOMMON_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "xkbcommon"` + fi + # Put the nasty error message in config.log where it belongs + echo "$XKBCOMMON_PKG_ERRORS" >&5 + + + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: libxkbcommon not found, key codes in key events may be incorrect" >&5 +$as_echo "$as_me: WARNING: libxkbcommon not found, key codes in key events may be incorrect" >&2;} + + +elif test $pkg_failed = untried; then + + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: libxkbcommon not found, key codes in key events may be incorrect" >&5 +$as_echo "$as_me: WARNING: libxkbcommon not found, key codes in key events may be incorrect" >&2;} + + +else + XKBCOMMON_CFLAGS=$pkg_cv_XKBCOMMON_CFLAGS + XKBCOMMON_LIBS=$pkg_cv_XKBCOMMON_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + CFLAGS="$XKBCOMMON_CFLAGS $CFLAGS" + CXXFLAGS="$XKBCOMMON_CFLAGS $CXXFLAGS" + GUI_TK_LIBRARY="$GUI_TK_LIBRARY $XKBCOMMON_LIBS" + $as_echo "#define HAVE_XKBCOMMON 1" >>confdefs.h + + +fi + fi +fi + if test "$wxUSE_SECRETSTORE" = "yes"; then if test "$wxUSE_MSW" != "1" -a "$wxUSE_OSX_COCOA" != 1; then diff --git a/configure.in b/configure.in index 7222e989b2..2ad1197b0b 100644 --- a/configure.in +++ b/configure.in @@ -5013,6 +5013,25 @@ if test "$wxUSE_FSWATCHER" = "yes"; then fi fi +dnl --------------------------------------------------------------------------- +dnl xkbcommon library for key code translations in wxGTK +dnl --------------------------------------------------------------------------- +if test "$wxUSE_GTK" = 1; then + if test "$USE_WIN32" != 1 -a "$USE_DARWIN" != 1; then + PKG_CHECK_MODULES(XKBCOMMON, [xkbcommon], + [ + CFLAGS="$XKBCOMMON_CFLAGS $CFLAGS" + CXXFLAGS="$XKBCOMMON_CFLAGS $CXXFLAGS" + GUI_TK_LIBRARY="$GUI_TK_LIBRARY $XKBCOMMON_LIBS" + AC_DEFINE(HAVE_XKBCOMMON) + ], + [ + AC_MSG_WARN([libxkbcommon not found, key codes in key events may be incorrect]) + ] + ) + fi +fi + dnl --------------------------------------------------------------------------- dnl Secret storage dnl --------------------------------------------------------------------------- diff --git a/setup.h.in b/setup.h.in index 41321542dc..edb4015f1e 100644 --- a/setup.h.in +++ b/setup.h.in @@ -1109,6 +1109,9 @@ /* Define if setpriority() is available. */ #undef HAVE_SETPRIORITY +/* Define if xkbcommon is available */ +#undef HAVE_XKBCOMMON + /* Define if xlocale.h header file exists. */ #undef HAVE_XLOCALE_H diff --git a/src/gtk/window.cpp b/src/gtk/window.cpp index cf7c7a0705..4a892098b7 100644 --- a/src/gtk/window.cpp +++ b/src/gtk/window.cpp @@ -24,6 +24,7 @@ #include "wx/settings.h" #include "wx/msgdlg.h" #include "wx/math.h" + #include "wx/module.h" #endif #include "wx/display.h" @@ -54,6 +55,12 @@ using namespace wxGTKImpl; typedef guint KeySym; #endif +// Use libxkbcommon for key code translation if we need and have it. +#if (defined (GDK_WINDOWING_X11) || defined (GDK_WINDOWING_WAYLAND)) && defined (HAVE_XKBCOMMON) +#define wxHAS_XKB +#include +#endif + #ifdef __WXGTK4__ #define wxGTK_HAS_COMPOSITING_SUPPORT 0 #else @@ -225,6 +232,80 @@ static wxWindowGTK *gs_deferredFocusOut = nullptr; GdkEvent *g_lastMouseEvent = nullptr; int g_lastButtonNumber = 0; +#ifdef wxHAS_XKB +namespace +{ + +// Global data used for raw key codes translation. +class XkbData +{ +public: + XkbData() = default; + + // Get the state pointer allocating it on demand if necessary. + xkb_state* GetState() + { + if ( !m_state ) + { + m_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + struct xkb_rule_names names = {0}; + names.layout = "us"; + m_keymap = xkb_keymap_new_from_names(m_ctx, &names, XKB_KEYMAP_COMPILE_NO_FLAGS); + m_state = xkb_state_new(m_keymap); + } + + return m_state; + } + + // Called by wxXKBModule::OnExit() to free all our data. + void Free() + { + if ( m_state ) + { + xkb_state_unref(m_state); + m_state = nullptr; + } + + if ( m_keymap ) + { + xkb_keymap_unref(m_keymap); + m_keymap = nullptr; + } + + if ( m_ctx ) + { + xkb_context_unref(m_ctx); + m_ctx = nullptr; + } + } + +private: + xkb_context *m_ctx = nullptr; + xkb_keymap *m_keymap = nullptr; + xkb_state *m_state = nullptr; + + wxDECLARE_NO_COPY_CLASS(XkbData); +}; + +XkbData gs_xkbData; + +} // anonymous namespace + +// wxXKBModule: used for freeing global xkb data +class wxXKBModule : public wxModule +{ +public: + bool OnInit() override { return true; } + void OnExit() override { gs_xkbData.Free(); } + +private: + wxDECLARE_DYNAMIC_CLASS(wxXKBModule); +}; + +wxIMPLEMENT_DYNAMIC_CLASS(wxXKBModule, wxModule); + +#endif // wxHAS_XKB + #ifdef __WXGTK3__ static GList* gs_sizeRevalidateList; static GSList* gs_setSizeRequestList; @@ -1025,7 +1106,38 @@ wxTranslateGTKKeyEventToWx(wxKeyEvent& event, : wxT("press"), static_cast(keysym)); - long key_code = wxTranslateKeySymToWXKey(keysym, false /* !isChar */); + long key_code = 0; + +#ifdef wxHAS_XKB + if ( gdk_keyval_to_unicode(gdk_event->keyval) ) + { + // If the event has the corresponding Unicode char, let us set key_code + // to character that is generated by that key in the US keyboard layout + // (as wxMSW does and because it's useful to be able to identify the + // key independently of the current keyboard layout). + char key_code_str[64]; + xkb_state_key_get_utf8(gs_xkbData.GetState(), + gdk_event->hardware_keycode, + key_code_str, + sizeof(key_code_str)); + if ( strlen(key_code_str) == 1 ) + { + key_code = key_code_str[0]; + + // If key_code is a Latin char, it should be in upper register to + // match the other ports. + if (islower(key_code)) + { + key_code = toupper(key_code); + } + } + } +#endif // wxHAS_XKB + + if ( !key_code ) + { + key_code = wxTranslateKeySymToWXKey(keysym, false /* !isChar */); + } if ( !key_code ) { @@ -1296,14 +1408,25 @@ gtk_window_key_press_callback( GtkWidget *WXUNUSED(widget), } } - if ( key_code ) + const auto uniChar = gdk_keyval_to_unicode(keysym); + if ( key_code || uniChar ) { wxKeyEvent eventChar(wxEVT_CHAR, event); - wxLogTrace(TRACE_KEYS, wxT("Char event: %ld"), key_code); + if ( event.ControlDown() && isalpha(event.m_keyCode) ) + { + // Ctrl+letter is handled specially by AdjustCharEventKeyCodes(). + eventChar.m_keyCode = event.m_keyCode; + eventChar.m_uniChar = event.m_uniChar; + } + else + { + // use Unicode values + eventChar.m_keyCode = key_code; + eventChar.m_uniChar = uniChar; + } - eventChar.m_keyCode = key_code; - eventChar.m_uniChar = gdk_keyval_to_unicode(key_code); + wxLogTrace(TRACE_KEYS, wxT("Char event: %ld"), eventChar.m_keyCode); AdjustCharEventKeyCodes(eventChar);