끝글자 버그를 고칩시다 2 - wxWidgets

Fri, 28 Jun 2019 23:55

끝글자 버그를 고칩시다 1 - ibus 에 이어서 계속되는 글입니다.

wxWidgets 끝글자 버그

https://packages.debian.org/buster/libwxgtk3.0-gtk3-0v5 패키지를 찾아보니 libwxgtk3.0 라는 소스코드가 따로 있는게 아니라 http://deb.debian.org/debian/pool/main/w/wxwidgets3.0/wxwidgets3.0_3.0.4+dfsg.orig.tar.xz 소스코드로부터 만들어진 패키지임을 알 수 있습니다.
이 방대한 코드에 놀랄 필요가 없습니다. wxWidgets 는 리눅스에서 사용하기 위해 gtk 를 사용한다는 것을 알 수 있고 gtk 는 입력 방법으로 https://developer.gnome.org/gtk3/stable/GtkIMContext.html 을 사용합니다. 따라서 그 함수들을 검색하면 됩니다.

검색하는 방법은 다음과 같습니다.

grep -nHIrF -- gtk_im_context_

이렇게 찾으면 됩니다. geany 에서 찾으면 편합니다.
해당 버그는 마우스 클릭할 때 입력기를 reset 하지 않아서 발생하는 버그입니다.
확인해보니 GtkEntry, GtkTextView 에는 문제가 없는데, wxWidgets 에서 GtkEntry, GtkTextView 에 있는 GtkIMContext 를 잘못 다루어 버그가 발생한 것이었습니다. 그래서 GtkEntry, GtkTextView가 키 이벤트를 처리하도록 다음처럼 코드를 수정합니다.
단순히 return false 함으로써 해결되었습니다.

diff --git a/src/gtk/textctrl.cpp b/src/gtk/textctrl.cpp
index 02bd068..6a53aeb 100644
--- a/src/gtk/textctrl.cpp
+++ b/src/gtk/textctrl.cpp
@@ -862,24 +862,7 @@ GtkEntry *wxTextCtrl::GetEntry() const
 
 int wxTextCtrl::GTKIMFilterKeypress(GdkEventKey* event) const
 {
-    if (IsSingleLine())
-        return wxTextEntry::GTKIMFilterKeypress(event);
-
-    int result;
-#if GTK_CHECK_VERSION(2, 22, 0)
-#ifndef __WXGTK3__
-    result = false;
-    if (gtk_check_version(2,22,0) == NULL)
-#endif
-    {
-        result = gtk_text_view_im_context_filter_keypress(GTK_TEXT_VIEW(m_text), event);
-    }
-#else // GTK+ < 2.22
-    wxUnusedVar(event);
-    result = false;
-#endif // GTK+ 2.22+
-
-    return result;
+    return false;
 }
 
 // ----------------------------------------------------------------------------
diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp
index d4eb406..6bb6d59 100644
--- a/src/gtk/textentry.cpp
+++ b/src/gtk/textentry.cpp
@@ -417,21 +417,7 @@ void wxTextEntry::SendMaxLenEvent()
 
 int wxTextEntry::GTKIMFilterKeypress(GdkEventKey* event) const
 {
-    int result;
-#if GTK_CHECK_VERSION(2, 22, 0)
-#ifndef __WXGTK3__
-    result = false;
-    if (gtk_check_version(2,22,0) == NULL)
-#endif
-    {
-        result = gtk_entry_im_context_filter_keypress(GetEntry(), event);
-    }
-#else // GTK+ < 2.22
-    wxUnusedVar(event);
-    result = false;
-#endif // GTK+ 2.22+
-
-    return result;
+    return false;
 }

그 후 컴파일한 다음 poedit, 4pane 을 실행해보니 버그가 해결되었음을 알 수 있습니다.
끝글자 버그는 단순한 버그입니다. 기술적으로 어려운 점은 없는데 어플을 컴파일하는데 시간이 오래 걸리고 어플 프로젝트에 버그 리포팅하고 개발자들을 이해시키고 답변을 기다리고 패치가 적용되는데까지 오랜 시간이 걸린다는 것이 어려운 점이죠.

wxWidgets 프로젝트에 패치를 보냈습니다.
Let GTK itself handle key events
https://github.com/wxWidgets/wxWidgets/pull/1357

이렇게 패치를 보냈었는데, 몇몇 문제가 있어서 다시 작성했습니다.

Use GtkEventController for input method
https://github.com/wxWidgets/wxWidgets/pull/1367

아래 패치는 wxWidgets 3.0.4 기준입니다. .h 에 멤버를 추가하면 바이너리 호환성이 깨지기 때문에 g_object_set_data_full() 함수를 사용하였습니다.

diff --git a/src/gtk/textctrl.cpp b/src/gtk/textctrl.cpp
index 02bd068..43ed914 100644
--- a/src/gtk/textctrl.cpp
+++ b/src/gtk/textctrl.cpp
@@ -750,6 +750,12 @@ bool wxTextCtrl::Create( wxWindow *parent,
 
     m_focusWidget = m_text;
 
+#if GTK_CHECK_VERSION(3, 24, 0)
+    g_object_set_data_full (G_OBJECT (m_text), "im-filter",
+                            gtk_event_controller_key_new (m_text),
+                            (GDestroyNotify) g_object_unref);
+#endif
+
     PostCreation(size);
 
     if (multi_line)
@@ -862,6 +868,12 @@ GtkEntry *wxTextCtrl::GetEntry() const
 
 int wxTextCtrl::GTKIMFilterKeypress(GdkEventKey* event) const
 {
+#if GTK_CHECK_VERSION(3, 24, 0)
+    GtkEventController *im_filter;
+    im_filter = (GtkEventController*) g_object_get_data (G_OBJECT (m_text), "im-filter");
+    return gtk_event_controller_handle_event (im_filter, (GdkEvent*) event);
+#endif
+
     if (IsSingleLine())
         return wxTextEntry::GTKIMFilterKeypress(event);
 
diff --git a/src/gtk/window.cpp b/src/gtk/window.cpp
index 96b4544..d67cd2c 100644
--- a/src/gtk/window.cpp
+++ b/src/gtk/window.cpp
@@ -1077,19 +1077,7 @@ gtk_window_key_press_callback( GtkWidget *WXUNUSED(widget),
 
 int wxWindowGTK::GTKIMFilterKeypress(GdkEventKey* event) const
 {
-    return m_imContext ? gtk_im_context_filter_keypress(m_imContext, event)
-                       : FALSE;
-}
-
-extern "C" {
-static void
-gtk_wxwindow_commit_cb (GtkIMContext * WXUNUSED(context),
-                        const gchar  *str,
-                        wxWindow     *window)
-{
-    // Ignore the return value here, it doesn't matter for the "commit" signal.
-    window->GTKDoInsertTextFromIM(str);
-}
+    return FALSE;
 }
 
 bool wxWindowGTK::GTKDoInsertTextFromIM(const char* str)
@@ -2065,15 +2053,6 @@ gtk_window_grab_broken( GtkWidget*,
 }
 #endif
 
-//-----------------------------------------------------------------------------
-// "unrealize"
-//-----------------------------------------------------------------------------
-
-static void unrealize(GtkWidget*, wxWindow* win)
-{
-    win->GTKHandleUnrealize();
-}
-
 #if GTK_CHECK_VERSION(3,8,0)
 //-----------------------------------------------------------------------------
 // "layout" from GdkFrameClock
@@ -2107,22 +2086,6 @@ void wxWindowGTK::GTKHandleRealized()
 {
     GdkWindow* const window = GTKGetDrawingWindow();
 
-    if (m_wxwindow)
-    {
-        if (m_imContext == NULL)
-        {
-            // Create input method handler
-            m_imContext = gtk_im_multicontext_new();
-
-            // Cannot handle drawing preedited text yet
-            gtk_im_context_set_use_preedit(m_imContext, false);
-
-            g_signal_connect(m_imContext,
-                "commit", G_CALLBACK(gtk_wxwindow_commit_cb), this);
-        }
-        gtk_im_context_set_client_window(m_imContext, window);
-    }
-
     // Use composited window if background is transparent, if supported.
     if (m_backgroundStyle == wxBG_STYLE_TRANSPARENT)
     {
@@ -2168,15 +2131,6 @@ void wxWindowGTK::GTKHandleRealized()
     GTKUpdateCursor(false, true);
 }
 
-void wxWindowGTK::GTKHandleUnrealize()
-{
-    if (m_wxwindow)
-    {
-        if (m_imContext)
-            gtk_im_context_set_client_window(m_imContext, NULL);
-    }
-}
-
 // ----------------------------------------------------------------------------
 // this wxWindowBase function is implemented here (in platform-specific file)
 // because it is static and so couldn't be made virtual
@@ -2330,7 +2284,6 @@ void wxWindowGTK::Init()
 
     m_clipPaintRegion = false;
 
-    m_imContext = NULL;
     m_imKeyEvent = NULL;
 
     m_dirtyTabOrder = false;
@@ -2518,13 +2471,6 @@ wxWindowGTK::~wxWindowGTK()
     // destroy children before destroying this window itself
     DestroyChildren();
 
-    // delete before the widgets to avoid a crash on solaris
-    if ( m_imContext )
-    {
-        g_object_unref(m_imContext);
-        m_imContext = NULL;
-    }
-
 #ifdef __WXGTK3__
     if (m_styleProvider)
         g_object_unref(m_styleProvider);
@@ -2660,7 +2606,6 @@ void wxWindowGTK::PostCreation()
         g_signal_connect (connect_widget, "realize",
                           G_CALLBACK (gtk_window_realized_callback), this);
     }
-    g_signal_connect(connect_widget, "unrealize", G_CALLBACK(unrealize), this);
 
     if (!IsTopLevel())
     {
@@ -3456,9 +3401,6 @@ bool wxWindowGTK::GTKHandleFocusIn()
                "handling focus_in event for %s(%p, %s)",
                GetClassInfo()->GetClassName(), this, GetLabel());
 
-    if (m_imContext)
-        gtk_im_context_focus_in(m_imContext);
-
     gs_currentFocus = this;
     gs_pendingFocus = NULL;
 
@@ -3519,9 +3461,6 @@ void wxWindowGTK::GTKHandleFocusOutNoDeferring()
                "handling focus_out event for %s(%p, %s)",
                GetClassInfo()->GetClassName(), this, GetLabel());
 
-    if (m_imContext)
-        gtk_im_context_focus_out(m_imContext);
-
     if ( gs_currentFocus != this )
     {
         // Something is terribly wrong, gs_currentFocus is out of sync with the

제 생각에는 이 패치는 별다른 부작용이 없다고 생각합니다.
개인적으로 적용하셔 사용하시기 바랍니다.


다음 글에서 계속됩니다. 끝글자 버그를 고칩시다 3 - scintilla