Skip to content

Commit

Permalink
Workaround for IME and echo events on Linux (fixes godotengine#29 god…
Browse files Browse the repository at this point in the history
…otengine#7106 godotengine#9381):

    Check each event in the queue to send echo event even if IME is
    activated.
  • Loading branch information
sowfelicity committed Jun 26, 2017
1 parent 8caa21a commit 62fe640
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 29 deletions.
3 changes: 3 additions & 0 deletions platform/x11/godot_x11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include <limits.h>
#include <locale.h>
#include <stdlib.h>
#include <unistd.h>

Expand All @@ -38,6 +39,8 @@ int main(int argc, char *argv[]) {

OS_X11 os;

setlocale(LC_CTYPE, "");

char *cwd = (char *)malloc(PATH_MAX);
getcwd(cwd, PATH_MAX);

Expand Down
189 changes: 160 additions & 29 deletions platform/x11/os_x11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@

#undef CursorShape

typedef struct _repeat_data {
XEvent *event;
bool repeat;
} repeat_data;

int OS_X11::get_video_driver_count() const {
return 1;
}
Expand All @@ -93,6 +98,7 @@ const char *OS_X11::get_audio_driver_name(int p_driver) const {

void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {

long im_event_mask = 0;
last_button_state = 0;

xmbstring = NULL;
Expand All @@ -113,7 +119,10 @@ void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_au
/** XLIB INITIALIZATION **/
x11_display = XOpenDisplay(NULL);

char *modifiers = XSetLocaleModifiers("@im=none");
char *modifiers = XSetLocaleModifiers("");
if (modifiers == NULL) {
modifiers = XSetLocaleModifiers("@im=none");
}
if (modifiers == NULL) {
WARN_PRINT("Error setting locale modifiers");
}
Expand Down Expand Up @@ -153,6 +162,14 @@ void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_au
WARN_PRINT("XOpenIM failed");
xim_style = 0L;
} else {
::XIMCallback im_destroy_callback;
im_destroy_callback.client_data = (::XPointer)(this);
im_destroy_callback.callback = (::XIMProc)(xim_destroy_callback);
if (XSetIMValues(xim, XNDestroyCallback, &im_destroy_callback,
NULL) != NULL) {
WARN_PRINT("Error setting XIM destroy callback");
}

::XIMStyles *xim_styles = NULL;
xim_style = 0L;
char *imvalret = NULL;
Expand Down Expand Up @@ -303,7 +320,8 @@ void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_au
StructureNotifyMask |
SubstructureNotifyMask | SubstructureRedirectMask |
FocusChangeMask | PropertyChangeMask |
ColormapChangeMask | OwnerGrabButtonMask;
ColormapChangeMask | OwnerGrabButtonMask |
im_event_mask;

XChangeWindowAttributes(x11_display, x11_window, CWEventMask, &new_attr);

Expand All @@ -327,6 +345,16 @@ void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_au
if (xim && xim_style) {

xic = XCreateIC(xim, XNInputStyle, xim_style, XNClientWindow, x11_window, XNFocusWindow, x11_window, (char *)NULL);
if (XGetICValues(xic, XNFilterEvents, &im_event_mask, NULL) != NULL) {
WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value");
XDestroyIC(xic);
xic = NULL;
}
if (xic) {
XSetICFocus(xic);
} else {
WARN_PRINT("XCreateIC couldn't create xic");
}
} else {

xic = NULL;
Expand Down Expand Up @@ -445,6 +473,54 @@ void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_au
_ensure_data_dir();
}

void OS_X11::xim_destroy_callback(::XIM im, ::XPointer client_data,
::XPointer call_data) {

WARN_PRINT("Input method stopped");
OS_X11 *os = reinterpret_cast<OS_X11 *>(client_data);
os->xim = NULL;
os->xic = NULL;
}

void OS_X11::set_ime_position(short x, short y) {

if (xic) {
return;
}
::XPoint spot;
spot.x = x;
spot.y = y;
XVaNestedList preedit_attr = XVaCreateNestedList(0,
XNSpotLocation, &spot,
NULL);
XSetICValues(xic,
XNPreeditAttributes, preedit_attr,
NULL);
XFree(preedit_attr);
return;
}

Bool OS_X11::check_repeat(::Display *display, ::XEvent *event,
::XPointer arg) {

repeat_data *data = reinterpret_cast<repeat_data *>(arg);

// I'm using a treshold of 5 msecs,
// since sometimes there seems to be a little
// jitter. I'm still not convinced that all this approach
// is correct, but the xorg developers are
// not very helpful today.

::Time tresh = ABS(event->xkey.time - data->event->xkey.time);
if (event->type == KeyPress && tresh < 5 &&
event->xkey.keycode == data->event->xkey.keycode) {
data->repeat = true;
// use the time from peek_event so it always works
}

//Check each event in the queue
return False;
}
void OS_X11::finalize() {

if (main_loop)
Expand Down Expand Up @@ -492,8 +568,12 @@ void OS_X11::finalize() {
XcursorImageDestroy(img[i]);
};

XDestroyIC(xic);
XCloseIM(xim);
if (xic) {
XDestroyIC(xic);
}
if (xim) {
XCloseIM(xim);
}

XCloseDisplay(x11_display);
if (xmbstring)
Expand Down Expand Up @@ -1041,9 +1121,60 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) {
xmblen = 8;
}

keysym_unicode = keysym_keycode;

if (xkeyevent->type == KeyPress && xic) {

Status status;
#ifdef X_HAVE_UTF8_STRING
int utf8len = 8;
char *utf8string = (char *)memalloc(sizeof(char) * utf8len);
int utf8bytes = Xutf8LookupString(xic, xkeyevent, utf8string,
utf8len - 1, &keysym_unicode, &status);
if (status == XBufferOverflow) {
utf8len = utf8bytes + 1;
utf8string = (char *)memrealloc(utf8string, utf8len);
utf8bytes = Xutf8LookupString(xic, xkeyevent, utf8string,
utf8len - 1, &keysym_unicode, &status);
}
utf8string[utf8bytes] = '\0';

if (status == XLookupChars) {
bool keypress = xkeyevent->type == KeyPress;
unsigned int keycode = KeyMappingX11::get_keycode(keysym_keycode);
String tmp;
tmp.parse_utf8(utf8string, utf8bytes);
for (int i = 0; i < tmp.length(); i++) {
Ref<InputEventKey> k;
k.instance();
if (keycode == 0 && tmp[i] == 0) {
continue;
}

get_key_modifier_state(xkeyevent->state, k);

k->set_unicode(tmp[i]);

k->set_pressed(keypress);

if (keycode >= 'a' && keycode <= 'z')
keycode -= 'a' - 'A';
k->set_scancode(keycode);

k->set_echo(p_echo);

if (k->get_scancode() == KEY_BACKTAB) {
//make it consistent across platforms.
k->set_scancode(KEY_TAB);
k->set_shift(true);
}

input->parse_input_event(k);
}
return;
}
memfree(utf8string);
#else
do {

int mnbytes = XmbLookupString(xic, xkeyevent, xmbstring, xmblen - 1, &keysym_unicode, &status);
Expand All @@ -1054,6 +1185,7 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) {
xmbstring = (char *)memrealloc(xmbstring, xmblen);
}
} while (status == XBufferOverflow);
#endif
}

/* Phase 2, obtain a pigui keycode from the keysym */
Expand Down Expand Up @@ -1082,11 +1214,6 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) {

bool keypress = xkeyevent->type == KeyPress;

if (xkeyevent->type == KeyPress && xic) {
if (XFilterEvent((XEvent *)xkeyevent, x11_window))
return;
}

if (keycode == 0 && unicode == 0)
return;

Expand Down Expand Up @@ -1116,27 +1243,17 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) {
// so this call won't block.
if (XPending(x11_display) > 0) {
XEvent peek_event;
XPeekEvent(x11_display, &peek_event);

// I'm using a treshold of 5 msecs,
// since sometimes there seems to be a little
// jitter. I'm still not convinced that all this approach
// is correct, but the xorg developers are
// not very helpful today.

::Time tresh = ABS(peek_event.xkey.time - xkeyevent->time);
if (peek_event.type == KeyPress && tresh < 5) {
KeySym rk;
XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, NULL);
if (rk == keysym_keycode) {
XEvent event;
XNextEvent(x11_display, &event); //erase next event
handle_key_event((XKeyEvent *)&event, true);
return; //ignore current, echo next
}
repeat_data arg;
arg.event = (XEvent *)xkeyevent;
arg.repeat = false;
XCheckIfEvent(x11_display, &peek_event, check_repeat,
(XPointer)&arg);
if (arg.repeat) {
XEvent event;
XNextEvent(x11_display, &event); //erase next event
handle_key_event((XKeyEvent *)&event, true);
return; //ignore current, echo next
}

// use the time from peek_event so it always works
}

// save the time to check for echo when keypress happens
Expand Down Expand Up @@ -1253,6 +1370,10 @@ void OS_X11::process_xevents() {
XEvent event;
XNextEvent(x11_display, &event);

if (XFilterEvent(&event, None)) {
continue;
}

switch (event.type) {
case Expose:
Main::force_redraw();
Expand Down Expand Up @@ -1295,6 +1416,9 @@ void OS_X11::process_xevents() {
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync, x11_window, None, CurrentTime);
}
if (xic) {
XSetICFocus(xic);
}
break;

case FocusOut:
Expand All @@ -1308,9 +1432,16 @@ void OS_X11::process_xevents() {
}
XUngrabPointer(x11_display, CurrentTime);
}
if (xic) {
XUnsetICFocus(xic);
}
break;

case ConfigureNotify:
if (xic) {
// Not portable.
set_ime_position(0, 1);
}
/* call resizeGLScene only if our window-size changed */

if ((event.xconfigure.width == current_videomode.width) &&
Expand Down
6 changes: 6 additions & 0 deletions platform/x11/os_x11.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ class OS_X11 : public OS_Unix {
::XIC xic;
::XIM xim;
::XIMStyle xim_style;
static void xim_destroy_callback(::XIM im, ::XPointer client_data,
::XPointer call_data);
void set_ime_position(short x, short y);
static Bool check_repeat(::Display *display, ::XEvent *event,
::XPointer arg);

Point2i last_mouse_pos;
bool last_mouse_pos_valid;
Point2i last_click_pos;
Expand Down

0 comments on commit 62fe640

Please sign in to comment.