1 /*
   2  * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 #include "glass_window.h"
  26 #include "glass_general.h"
  27 #include "glass_gtkcompat.h"
  28 
  29 #include <cstring>
  30 #include <cstdlib>
  31 
  32 bool WindowContextBase::hasIME() {
  33     return xim.enabled;
  34 }
  35 
  36 static XKeyPressedEvent convert_event(GdkEventKey *event) {
  37     XKeyPressedEvent result;
  38     memset(&result, 0, sizeof (result));
  39 
  40     result.type = (event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease;
  41     result.send_event = event->send_event;
  42     result.display = gdk_x11_display_get_xdisplay(glass_gdk_window_get_display(event->window));
  43     result.window = result.subwindow = GDK_WINDOW_XID(event->window);
  44     result.root = GDK_WINDOW_XID(gdk_screen_get_root_window(glass_gdk_window_get_screen(event->window)));
  45     result.time = event->time;
  46     result.state = event->state;
  47     result.keycode = event->hardware_keycode;
  48     result.same_screen = True;
  49 
  50     return result;
  51 }
  52 
  53 bool WindowContextBase::im_filter_keypress(GdkEventKey* event) {
  54     static size_t buf_len = 12;
  55     static char *buffer = NULL;
  56 
  57     if (buffer == NULL) {
  58         buffer = (char*)malloc(buf_len * sizeof (char));
  59     }
  60 
  61     KeySym keysym;
  62     Status status;
  63     XKeyPressedEvent xevent = convert_event(event);
  64     if (XFilterEvent((XEvent*) & xevent, GDK_WINDOW_XID(gdk_window))) {
  65         return TRUE;
  66     }
  67 
  68     if (event->type == GDK_KEY_RELEASE) {
  69         process_key(event);
  70         return TRUE;
  71     }
  72 
  73     int len = Xutf8LookupString(xim.ic, &xevent, buffer, buf_len - 1, &keysym, &status);
  74     if (status == XBufferOverflow) {
  75         buf_len = len + 1;
  76         buffer = (char*)realloc(buffer, buf_len * sizeof (char));
  77         len = Xutf8LookupString(xim.ic, &xevent, buffer, buf_len - 1,
  78                 &keysym, &status);
  79     }
  80     switch (status) {
  81         case XLookupKeySym:
  82         case XLookupBoth:
  83             if (xevent.keycode) {
  84                 //process it as a normal key
  85                 process_key(event);
  86                 break;
  87             }
  88             // fall-through
  89         case XLookupChars:
  90             buffer[len] = 0;
  91             jstring str = mainEnv->NewStringUTF(buffer);
  92             EXCEPTION_OCCURED(mainEnv);
  93             jsize slen = mainEnv->GetStringLength(str);
  94             mainEnv->CallVoidMethod(jview,
  95                     jViewNotifyInputMethod,
  96                     str,
  97                     NULL, NULL, NULL,
  98                     slen,
  99                     slen,
 100                     0);
 101             LOG_EXCEPTION(mainEnv)
 102 
 103             break;
 104     }
 105 
 106     return TRUE;
 107 }
 108 
 109 bool WindowContextBase::filterIME(GdkEvent * event) {
 110     if (!hasIME()) {
 111         return false;
 112     }
 113 
 114     switch (event->type) {
 115         case GDK_KEY_PRESS:
 116         case GDK_KEY_RELEASE:
 117             return im_filter_keypress(reinterpret_cast<GdkEventKey*> (event));
 118         default:
 119             return FALSE;
 120     }
 121 }
 122 
 123 //Note: this function must return int, despite the fact it doesn't conform to XIMProc type.
 124 // This is required in documentation of XIM
 125 static int im_preedit_start(XIM im_xim, XPointer client, XPointer call) {
 126     (void)im_xim;
 127     (void)call;
 128 
 129     mainEnv->CallVoidMethod((jobject) client, jViewNotifyPreeditMode, JNI_TRUE);
 130     CHECK_JNI_EXCEPTION_RET(mainEnv, -1);
 131     return -1; // No restrictions
 132 }
 133 
 134 static void im_preedit_done(XIM im_xim, XPointer client, XPointer call) {
 135     (void)im_xim;
 136     (void)call;
 137 
 138     mainEnv->CallVoidMethod((jobject) client, jViewNotifyPreeditMode, JNI_FALSE);
 139     CHECK_JNI_EXCEPTION(mainEnv);
 140 }
 141 
 142 static void im_preedit_draw(XIM im_xim, XPointer client, XPointer call) {
 143     (void)im_xim;
 144     (void)call;
 145 
 146     XIMPreeditDrawCallbackStruct *data = (XIMPreeditDrawCallbackStruct*) call;
 147     jstring text = NULL;
 148     if (data->text != NULL && data->text->string.multi_byte != NULL) {
 149         if (data->text->encoding_is_wchar) {
 150             size_t csize = wcstombs(NULL, data->text->string.wide_char, 0);
 151             char *ctext = new char[csize + 1];
 152             wcstombs(ctext, data->text->string.wide_char, csize + 1);
 153             text = mainEnv->NewStringUTF(ctext);
 154             EXCEPTION_OCCURED(mainEnv);
 155             delete[] ctext;
 156         } else {
 157             text = mainEnv->NewStringUTF(data->text->string.multi_byte);
 158             EXCEPTION_OCCURED(mainEnv);
 159         }
 160     }
 161 
 162     mainEnv->CallVoidMethod((jobject)client, jViewNotifyInputMethodDraw,
 163             text, data->chg_first, data->chg_length, data->caret);
 164     CHECK_JNI_EXCEPTION(mainEnv)
 165 }
 166 
 167 static void im_preedit_caret(XIM im_xim, XPointer client, XPointer call) {
 168     (void)im_xim;
 169 
 170     XIMPreeditCaretCallbackStruct *data = (XIMPreeditCaretCallbackStruct*) call;
 171     mainEnv->CallVoidMethod((jobject)client, jViewNotifyInputMethodCaret,
 172             data->position, data->direction, data->style);
 173     CHECK_JNI_EXCEPTION(mainEnv)
 174 }
 175 
 176 static XIMStyle get_best_supported_style(XIM im_xim)
 177 {
 178     XIMStyles* styles;
 179     int i;
 180     XIMStyle result = 0;
 181 
 182     if (XGetIMValues(im_xim, XNQueryInputStyle, &styles, NULL) != NULL) { // NULL means it's OK
 183         return 0;
 184     }
 185 
 186     for (i = 0; i < styles->count_styles; ++i) {
 187         if (styles->supported_styles[i] == (XIMPreeditCallbacks | XIMStatusNothing)
 188                 || styles->supported_styles[i] == (XIMPreeditNothing | XIMStatusNothing)) {
 189             result = styles->supported_styles[i];
 190             break;
 191         }
 192     }
 193 
 194     XFree(styles);
 195 
 196     return result;
 197 }
 198 
 199 void WindowContextBase::enableOrResetIME() {
 200     Display *display = gdk_x11_display_get_xdisplay(glass_gdk_window_get_display(gdk_window));
 201     if (xim.im == NULL || xim.ic == NULL) {
 202         xim.im = XOpenIM(display, NULL, NULL, NULL);
 203         if (xim.im == NULL) {
 204             return;
 205         }
 206 
 207         XIMStyle styles = get_best_supported_style(xim.im);
 208         if (styles == 0) {
 209             return;
 210         }
 211 
 212         XIMCallback startCallback = {(XPointer) jview, (XIMProc) im_preedit_start};
 213         XIMCallback doneCallback = {(XPointer) jview, im_preedit_done};
 214         XIMCallback drawCallback = {(XPointer) jview, im_preedit_draw};
 215         XIMCallback caretCallback = {(XPointer) jview, im_preedit_caret};
 216 
 217         XVaNestedList list = XVaCreateNestedList(0,
 218                 XNPreeditStartCallback, &startCallback,
 219                 XNPreeditDoneCallback, &doneCallback,
 220                 XNPreeditDrawCallback, &drawCallback,
 221                 XNPreeditCaretCallback, &caretCallback,
 222                 NULL);
 223 
 224         xim.ic = XCreateIC(xim.im,
 225                 XNInputStyle, styles,
 226                 XNClientWindow, GDK_WINDOW_XID(gdk_window),
 227                 XNPreeditAttributes, list,
 228                 NULL);
 229 
 230         XFree(list);
 231 
 232         if (xim.ic == NULL) {
 233             return;
 234         }
 235     }
 236 
 237     if (xim.enabled) { //called when changed focus to different input
 238         XmbResetIC(xim.ic);
 239     }
 240 
 241 
 242     XSetICFocus(xim.ic);
 243 
 244     xim.enabled = TRUE;
 245 }
 246 
 247 void WindowContextBase::disableIME() {
 248     if (xim.ic != NULL) {
 249         XUnsetICFocus(xim.ic);
 250     }
 251 }