Sublime Forum

Input method support

#10

Since user “csslayer” gives so many hints how to solve this problem, I’m wondering why this couldn’t be fixed yet.
It’s really an important issue for most CJK users.

0 Likes

#11

Still not solved.

build 2217 with fcitx…
it seems that it can not call the fcitx by the shortcut with Ctrl-Space. but change the fcitx’s shortcut from C-S to another. Problem remains…

0 Likes

#12

input methods(seems all 3rd party or system intergrated ) can’t follow cursor position in microsoft windows 7 and xp.
Jon, please consider fix this problem, every cjk user depend on input methods

0 Likes

#13

Still not works in CJK with ibus :frowning:
I hope this can be solved as soon as possible.

By the way, there is a package named “InputHelper” which can temporarily solve this issue. (Though it’s not quite convenient)

0 Likes

#14

I’m a sublime text2 user from China, it works fine in my windows, but in my fedora i can’t input any Chinese words :astonished: , it’s important for CJK user, please fix it.

0 Likes

#15

VIM is awesome! I’m not going to buy a Sublime until this bug has been fixed.

0 Likes

#16

This is a dirty fix but at least works. cursor position update also supported.

Use LD_PRELOAD to reimplement gtk_im_context_set_client_window and set im focus in.
use “gdk_region_get_clipbox” to catch the caret position. (It’s really difficult to find which function can catch the position…)

Here I made a assumption that the caret width is always 2, since it is 2.

the height is the “font glyph height”.

1, save below code to sublime_imfix.c

/*
sublime-imfix.c
Use LD_PRELOAD to interpose some function to fix sublime input method support for linux.
By Cjacker Huang <jianzhong.huang at i-soft.com.cn>

gcc -shared -o libsublime-imfix.so sublime_imfix.c  `pkg-config --libs --cflags gtk+-2.0` -fPIC
LD_PRELOAD=./libsublime-imfix.so sublime_text
*/
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
typedef GdkSegment GdkRegionBox;

struct _GdkRegion
{
  long size;
  long numRects;
  GdkRegionBox *rects;
  GdkRegionBox extents;
};

GtkIMContext *local_context;

void
gdk_region_get_clipbox (const GdkRegion *region,
            GdkRectangle    *rectangle)
{
  g_return_if_fail (region != NULL);
  g_return_if_fail (rectangle != NULL);

  rectangle->x = region->extents.x1;
  rectangle->y = region->extents.y1;
  rectangle->width = region->extents.x2 - region->extents.x1;
  rectangle->height = region->extents.y2 - region->extents.y1;
  GdkRectangle rect;
  rect.x = rectangle->x;
  rect.y = rectangle->y;
  rect.width = 0;
  rect.height = rectangle->height; 
  //The caret width is 2; 
  //Maybe sometimes we will make a mistake, but for most of the time, it should be the caret.
  if(rectangle->width == 2 && GTK_IS_IM_CONTEXT(local_context)) {
        gtk_im_context_set_cursor_location(local_context, rectangle);
  }
}

//this is needed, for example, if you input something in file dialog and return back the edit area
//context will lost, so here we set it again.

static GdkFilterReturn event_filter (GdkXEvent *xevent, GdkEvent *event, gpointer im_context)
{
    XEvent *xev = (XEvent *)xevent;
    if(xev->type == KeyRelease && GTK_IS_IM_CONTEXT(im_context)) {
       GdkWindow * win = g_object_get_data(G_OBJECT(im_context),"window");
       if(GDK_IS_WINDOW(win))
         gtk_im_context_set_client_window(im_context, win);
    }
    return GDK_FILTER_CONTINUE;
}

void gtk_im_context_set_client_window (GtkIMContext *context,
          GdkWindow    *window)
{
  GtkIMContextClass *klass;
  g_return_if_fail (GTK_IS_IM_CONTEXT (context));
  klass = GTK_IM_CONTEXT_GET_CLASS (context);
  if (klass->set_client_window)
    klass->set_client_window (context, window);

  if(!GDK_IS_WINDOW (window))
    return;
  g_object_set_data(G_OBJECT(context),"window",window);
  int width = gdk_window_get_width(window);
  int height = gdk_window_get_height(window);
  if(width != 0 && height !=0) {
    gtk_im_context_focus_in(context);
    local_context = context;
  }
  gdk_window_add_filter (window, event_filter, context); 
}

2, compile a shared library.

gcc -shared -o libsublime-imfix.so sublime_imfix.c  `pkg-config --libs --cflags gtk+-2.0` -fPIC

3, LD_PRELOAD it

LD_PRELOAD=./libsublime-imfix.so sublime_text
0 Likes

#17

.

0 Likes

#18

[quote=“cjacker”]This is a dirty fix but at least works. cursor location update still not works, since I really can not find a proper way to get the current caret loation relative to
GdkWidow, it seems sublime use a customized GtkWidget? I am not sure.

[/quote]

I’ve tested this in Archlinux with sublime version 2.0.1-2, It works quite well.

Thanks for your excellent job!!

0 Likes

#19

I’ve tested this in Archlinux with sublime version 2.0.1-2, It works quite well.

Thanks for your excellent job!![/quote]

original post had been updated to support cursor location update, please try it.

0 Likes

#20

Followed all of cjacker’s steps but they didn’t seem to work for me. No errors or anything, I just still don’t get anything from the IME. Even if I turn it on manurally, when I type the characters are still English, not the Japanese characters from the IME.

0 Likes

#21

[quote=“cjacker”]This is a dirty fix but at least works. cursor position update also supported.

Use LD_PRELOAD to reimplement gtk_im_context_set_client_window and set im focus in.
use “gdk_region_get_clipbox” to catch the caret position. (It’s really difficult to find which function can catch the position…)

[/quote]

I’ve tried your method and got the following result:

$ gcc -shared -o libsublime-imfix.so sublime_imfix.c `pkg-config --libs --cflags gtk+-2.0` -fPIC $ LD_PRELOAD=./libsublime-imfix.so sublime-text ERROR: ld.so: object './libsublime-imfix.so' from LD_PRELOAD cannot be preloaded: ignored.

$ uname -ai Linux tonytonyjan-Latitude-6430U 3.5.0-17-generic #28-Ubuntu SMP Tue Oct 9 19:31:23 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux

Is there any message I can give you?

0 Likes

#22

[quote=“cjacker”]This is a dirty fix but at least works. cursor position update also supported.

Use LD_PRELOAD to reimplement gtk_im_context_set_client_window and set im focus in.
use “gdk_region_get_clipbox” to catch the caret position. (It’s really difficult to find which function can catch the position…)
[/code][/quote]

I tried this on Ubuntu 12.04, it works but only work for FCITX not ibus.
I sure get cursor location update, but there still is some problem (actually a big one) - backspace will erase the text before cursor, not character in IME.

So, I guess I just went back to ibus since it makes me feel better :smile: Anyway, great work.

Another question: is it possible to bring this cursor location update feature to Windows version of sublime?
IME works better on Windows version of sublime, the only problem is cursor location update. If you can fix that, it will be really great.

Thanks dude, that works perfect!

0 Likes

#23

For Sublime Text 2, there was a nice plugin for supporting input method.
github.com/chikatoike/IMESuppor … DME_en.org
Now that Sublime Text 3 has threaded plugin system, the plugin no longer works properly, at least in my environment.
Because the plugin hooks the input, there are some side effects like BracketHighlighter becomes unworkable.
Please support the input method correctly, especially with Sublime Text 3 for Windows.

0 Likes

#24

I wasn’t able to use my compose key at all on Arch Linux running KDE with xim in ST2, I tried scim but it didn’t supposed custom compose keys from ~/.XCompose, but I was able to fix this for Linux by changing to scim-uim

To enable this for all GTK apps, put this into your ~/.xprofile after installing scim-uim, and then restart X:

GTK_IM_MODULE=uim-scim
Note that there are other ways to do this that make this work for all users on the computer and whatnot.
To run it just for ST2, or test it out without restarting, run this in your preferred console, set it as a bash alias, etc:

export GTK_IM_MODULE=uim-scim subl
Compose, and ~/.XCompose should now work.

0 Likes

#25

Sublime text used to be my favorite. Now due to this problem I’ve had to switch editors. So upsettting.

0 Likes

#26

I’ve fixed this problem: whitequark.org/blog/2014/04/14/x … lime-text/

0 Likes

#27

Here is the update solution to combine whitequark’s fix for XIM immodule.

My original codes only works with fcitx immodule.
Whitequark’s codes only workds with xim immodule and without cursor position update support.

Here is the better one to support GTK_IM_MODULE=fcitx or GTK_IM_MODULE=xim and both support cursor position update.

For how to interpose(I am still too lasy to explain it:-D), maybe you can refer to whitequark’s blog.

/*
sublime-imfix.c
Use LD_PRELOAD to interpose some function to fix sublime input method support for linux.
By Cjacker Huang <jianzhong.huang at i-soft.com.cn>
By whitequark@whitequark.org

How to compile:
gcc -shared -o libsublime-imfix.so sublime_imfix.c  `pkg-config --libs --cflags gtk+-2.0` -fPIC
How to use:
LD_PRELOAD=./libsublime-imfix.so sublime_text

Changes:
2014 06-09
1, Fix cursor position update for sublime text 3.
2, Combine the codes from whitequark(fix for xim immodule) and add cursor update support for XIM immodule.
*/

/*for RTLD_NEXT*/
#define _GNU_SOURCE

#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <assert.h>
#include <dlfcn.h>
#include <stdio.h>
#include <string.h>
#include <gtk/gtk.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#ifdef VERBOSE
#define DEBUG(fmt, ...) do { \
    FILE* err = fopen("/tmp/libsublime-immethod-fix.log", "a"); \
    if (err) { \
      fprintf(err, fmt, __VA_ARGS__); \
      fclose(err); \
    } \
  } while(0)
#else
#define DEBUG(fmt, ...)
#endif


typedef GdkSegment GdkRegionBox;

struct _GdkRegion
{
  long size;
  long numRects;
  GdkRegionBox *rects;
  GdkRegionBox extents;
};

GtkIMContext *local_context;


//this func is interposed to support cursor position update.
void
gdk_region_get_clipbox (const GdkRegion *region,
            GdkRectangle    *rectangle)
{
  g_return_if_fail (region != NULL);
  g_return_if_fail (rectangle != NULL);

  rectangle->x = region->extents.x1;
  rectangle->y = region->extents.y1;
  rectangle->width = region->extents.x2 - region->extents.x1;
  rectangle->height = region->extents.y2 - region->extents.y1;
  GdkRectangle rect;
  rect.x = rectangle->x;
  rect.y = rectangle->y;
  rect.width = 0;
  rect.height = rectangle->height;
  //The caret width is 2 in sublime text 2
  //And is 1 in sublime text 3.
  //Maybe sometimes we will make a mistake, but for most of the time, it should be the caret.
  if((rectangle->width == 2 || rectangle->width == 1)  && GTK_IS_IM_CONTEXT(local_context)) {
        gtk_im_context_set_cursor_location(local_context, rectangle);
  }
}

//this is needed, for example, if you input something in file dialog and return back the edit area
//context will lost, so here we set it again.
static GdkFilterReturn event_filter (GdkXEvent *xevent, GdkEvent *event, gpointer im_context)
{
    XEvent *xev = (XEvent *)xevent;
    if(xev->type == KeyRelease && GTK_IS_IM_CONTEXT(im_context)) {
       GdkWindow * win = g_object_get_data(G_OBJECT(im_context),"window");
       if(GDK_IS_WINDOW(win))
         gtk_im_context_set_client_window(im_context, win);
    }
    return GDK_FILTER_CONTINUE;
}

void gtk_im_context_set_client_window (GtkIMContext *context,
          GdkWindow    *window)
{
    GtkIMContextClass *klass;
    g_return_if_fail (GTK_IS_IM_CONTEXT (context));
    klass = GTK_IM_CONTEXT_GET_CLASS (context);
    if (klass->set_client_window)
        klass->set_client_window (context, window);

    //below is our interposed codes to save the context to local_context.
    if(!GDK_IS_WINDOW (window))
        return;
    g_object_set_data(G_OBJECT(context),"window",window);
    int width = gdk_window_get_width(window);
    int height = gdk_window_get_height(window);
    if(width != 0 && height !=0) {
        gtk_im_context_focus_in(context);
        local_context = context;
    }
    //only add this event_filter when using 'fcitx' immodule.
    //for xim immodule, this function is as same as original from gtk2.
    const gchar * immodule = g_getenv("GTK_IM_MODULE");
    if(immodule && !strcmp(immodule, "fcitx")) {
        gdk_window_add_filter (window, event_filter, context);
    }
}


/*below codes is from whitequark, fix for xim immodule*/

/* See gtkimcontextxim.c */
GType gtk_type_im_context_xim = 0;

#define GTK_TYPE_IM_CONTEXT_XIM            (gtk_type_im_context_xim)
#define GTK_IM_CONTEXT_XIM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_IM_CONTEXT_XIM, GtkIMContextXIM))
#define GTK_IM_CONTEXT_XIM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_IM_CONTEXT_XIM, GtkIMContextXIMClass))
#define GTK_IS_IM_CONTEXT_XIM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_IM_CONTEXT_XIM))
#define GTK_IS_IM_CONTEXT_XIM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_IM_CONTEXT_XIM))
#define GTK_IM_CONTEXT_XIM_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_IM_CONTEXT_XIM, GtkIMContextXIMClass))

typedef struct _GtkIMContextXIM       GtkIMContextXIM;
typedef struct _GtkIMContextXIMClass  GtkIMContextXIMClass;

struct _GtkIMContextXIMClass
{
  GtkIMContextClass parent_class;
};

typedef struct _StatusWindow StatusWindow;
typedef struct _GtkXIMInfo GtkXIMInfo;

struct _GtkIMContextXIM
{
  GtkIMContext object;

  GtkXIMInfo *im_info;

  gchar *locale;
  gchar *mb_charset;

  GdkWindow *client_window;
  GtkWidget *client_widget;

  /* The status window for this input context; we claim the
 *    * status window when we are focused and have created an XIC
 *       */
  StatusWindow *status_window;

  gint preedit_size;
  gint preedit_length;
  gunichar *preedit_chars;
  XIMFeedback *feedbacks;

  gint preedit_cursor;

  XIMCallback preedit_start_callback;
  XIMCallback preedit_done_callback;
  XIMCallback preedit_draw_callback;
  XIMCallback preedit_caret_callback;

  XIMCallback status_start_callback;
  XIMCallback status_done_callback;
  XIMCallback status_draw_callback;

  XIMCallback string_conversion_callback;

  XIC ic;

  guint filter_key_release : 1;
  guint use_preedit : 1;
  guint finalizing : 1;
  guint in_toplevel : 1;
  guint has_focus : 1;
};

static GClassInitFunc orig_gtk_im_context_xim_class_init;
static GType (*orig_g_type_module_register_type)(GTypeModule *,
                                                 GType, const gchar *,
                                                 const GTypeInfo *, GTypeFlags);
static gboolean (*orig_gtk_im_context_xim_filter_keypress)(GtkIMContext *context,
                                                           GdkEventKey *event);

static gboolean
hook_gtk_im_context_xim_filter_keypress(GtkIMContext *context, GdkEventKey *event) {
  GtkIMContextXIM *im_context_xim = GTK_IM_CONTEXT_XIM(context);
  if (!im_context_xim->client_window) {
    DEBUG("im_context_xim == %p\n", im_context_xim);
    DEBUG("event->window == %p\n", event->window);

    gtk_im_context_set_client_window(context, event->window);
  }

  return orig_gtk_im_context_xim_filter_keypress(context, event);
}

static void
hook_gtk_im_context_xim_class_init (GtkIMContextXIMClass *class) {
  orig_gtk_im_context_xim_class_init(class, NULL); /* wat? */

  GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);

  assert(!orig_gtk_im_context_xim_filter_keypress);
  orig_gtk_im_context_xim_filter_keypress = im_context_class->filter_keypress;
  im_context_class->filter_keypress = hook_gtk_im_context_xim_filter_keypress;
  DEBUG("orig_gtk_im_context_xim_filter_keypress: %p\n",
        orig_gtk_im_context_xim_filter_keypress);
}

GType
g_type_module_register_type (GTypeModule *module,
                             GType parent_type,
                             const gchar *type_name,
                             const GTypeInfo *type_info,
                             GTypeFlags flags) {
  if (!orig_g_type_module_register_type) {
    orig_g_type_module_register_type = dlsym(RTLD_NEXT, "g_type_module_register_type");
    assert(orig_g_type_module_register_type);
  }

  if (type_name && !strcmp(type_name, "GtkIMContextXIM")) {
    assert(!orig_gtk_im_context_xim_class_init);
    orig_gtk_im_context_xim_class_init = type_info->class_init;

    assert(sizeof(GtkIMContextXIM) == type_info->instance_size);

    const GTypeInfo hook_im_context_xim_info =
    {
      type_info->class_size,
      type_info->base_init,
      type_info->base_finalize,
      (GClassInitFunc) hook_gtk_im_context_xim_class_init,
      type_info->class_finalize,
      type_info->class_data,
      type_info->instance_size,
      type_info->n_preallocs,
      type_info->instance_init,
    };

    DEBUG("orig_gtk_im_context_xim_class_init: %p\n", orig_gtk_im_context_xim_class_init);

    gtk_type_im_context_xim =
      orig_g_type_module_register_type(module, parent_type, type_name,
                                       &hook_im_context_xim_info, flags);

    return gtk_type_im_context_xim;
  }

  return orig_g_type_module_register_type(module, parent_type, type_name, type_info, flags);
}
0 Likes

#28

thank you so much to whitequark and cjacker. i’ve just invested a few days getting familiar with Sublime Text and talking myself into the making the psychologically wrenching step of switching code editors, and then I ran into this problem, which is a total deal-breaker for me. the interposition works very nicely, but i do find it more than a little disconcerting that the developers seem to have no interest in this issue – even when the community has effectively discovered, diagnosed and solved it for them… i was already a little uncomfortable using a non-open-source code editor… think i’ll delay my purchase a bit longer and see how this plays out.

0 Likes

#29

i tried to fork chikatoike’s SublimeIBus https://github.com/chikatoike/SublimeIBus to make chinese/english hybrid input work in sublime 2 , but due to my technical skills, i can’t figure out how to get rid of the redundent return after you press enter. it’s 90% working i could say …

check my repo https://github.com/oglops/SublimeIBus

check this gyfcat screencast http://gfycat.com/HeartfeltEnlightenedGentoopenguin

0 Likes