diff --git a/build/tools/before_install.sh b/build/tools/before_install.sh index 92d95cadfd..9e673a573b 100755 --- a/build/tools/before_install.sh +++ b/build/tools/before_install.sh @@ -61,7 +61,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 a7160f9a50..5a3943bd70 100755 --- a/configure +++ b/configure @@ -955,6 +955,8 @@ GSPELL_LIBS GSPELL_CFLAGS LIBSECRET_LIBS LIBSECRET_CFLAGS +XKBCOMMON_LIBS +XKBCOMMON_CFLAGS GXX_VERSION LIBICONV CXXFLAGS_VISIBILITY @@ -1436,6 +1438,8 @@ WAYLAND_EGL_CFLAGS WAYLAND_EGL_LIBS MesaGL_CFLAGS MesaGL_LIBS +XKBCOMMON_CFLAGS +XKBCOMMON_LIBS LIBSECRET_CFLAGS LIBSECRET_LIBS GSPELL_CFLAGS @@ -2478,6 +2482,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 @@ -38043,6 +38051,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 "$WXGTK1" = "1"; then diff --git a/configure.in b/configure.in index e1f7a0c870..5ad4f62cbf 100644 --- a/configure.in +++ b/configure.in @@ -5688,6 +5688,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/docs/changes.txt b/docs/changes.txt index d524dbd0ff..a923f63481 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -253,6 +253,7 @@ All (GUI): wxGTK: +- Fix key codes with Shift/on non-US keyboards (Ivan Sorokin, #17643, #23379). - Dramatically optimize adding many items to wxChoice (Ian McInerney, #23443). - Improve document wxGLCanvas::CreateSurface() (Dan Gudmundsson, #23366). - Fix loading WebKit2 extension when using wxWebView (Scott Talbert, #23497). diff --git a/setup.h.in b/setup.h.in index fa74d8fbc0..985b01d545 100644 --- a/setup.h.in +++ b/setup.h.in @@ -1246,6 +1246,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 c0cdea63d2..499e749552 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,85 @@ static wxWindowGTK *gs_deferredFocusOut = NULL; GdkEvent *g_lastMouseEvent = NULL; int g_lastButtonNumber = 0; +#ifdef wxHAS_XKB +namespace +{ + +// Global data used for raw key codes translation. +class XkbData +{ +public: + XkbData() + { + m_ctx = NULL; + m_keymap = NULL; + m_state = NULL; + } + + // 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 = NULL; + } + + if ( m_keymap ) + { + xkb_keymap_unref(m_keymap); + m_keymap = NULL; + } + + if ( m_ctx ) + { + xkb_context_unref(m_ctx); + m_ctx = NULL; + } + } + +private: + xkb_context *m_ctx; + xkb_keymap *m_keymap; + xkb_state *m_state; + + wxDECLARE_NO_COPY_CLASS(XkbData); +}; + +XkbData gs_xkbData; + +} // anonymous namespace + +// wxXKBModule: used for freeing global xkb data +class wxXKBModule : public wxModule +{ +public: + bool OnInit() wxOVERRIDE { return true; } + void OnExit() wxOVERRIDE { 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; @@ -1028,7 +1114,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 ) { @@ -1308,16 +1425,29 @@ gtk_window_key_press_callback( GtkWidget *WXUNUSED(widget), } } - if ( key_code ) + const guint32 uniChar = gdk_keyval_to_unicode(keysym); + if ( key_code || uniChar ) { wxKeyEvent eventChar(wxEVT_CHAR, event); - wxLogTrace(TRACE_KEYS, wxT("Char event: %ld"), key_code); - - eventChar.m_keyCode = key_code; + if ( event.ControlDown() && isalpha(event.m_keyCode) ) + { + // Ctrl+letter is handled specially by AdjustCharEventKeyCodes(). + eventChar.m_keyCode = event.m_keyCode; #if wxUSE_UNICODE - eventChar.m_uniChar = gdk_keyval_to_unicode(key_code); + eventChar.m_uniChar = event.m_uniChar; #endif // wxUSE_UNICODE + } + else + { + // use Unicode values + eventChar.m_keyCode = key_code; +#if wxUSE_UNICODE + eventChar.m_uniChar = uniChar; +#endif // wxUSE_UNICODE + } + + wxLogTrace(TRACE_KEYS, wxT("Char event: %ld"), eventChar.m_keyCode); AdjustCharEventKeyCodes(eventChar);