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 
  26 #include "com_sun_glass_ui_View.h"
  27 #include "glass_window.h"
  28 #include "glass_general.h"
  29 #include "glass_gtkcompat.h"
  30 
  31 #include <cstring>
  32 #include <cstdlib>
  33 
  34 bool WindowContextBase::hasIME() {
  35     return xim.enabled;
  36 }
  37 
  38 static XKeyPressedEvent convert_event(GdkEventKey *event) {
  39     XKeyPressedEvent result;
  40     memset(&result, 0, sizeof (result));
  41 
  42     result.type = (event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease;
  43     result.send_event = event->send_event;
  44     result.display = gdk_x11_display_get_xdisplay(glass_gdk_window_get_display(event->window));
  45     result.window = result.subwindow = GDK_WINDOW_XID(event->window);
  46     result.root = GDK_WINDOW_XID(gdk_screen_get_root_window(glass_gdk_window_get_screen(event->window)));
  47     result.time = event->time;
  48     result.state = event->state;
  49     result.keycode = event->hardware_keycode;
  50     result.same_screen = True;
  51 
  52     return result;
  53 }
  54 
  55 bool WindowContextBase::im_filter_keypress(GdkEventKey* event) {
  56     static size_t buf_len = 12;
  57     static char *buffer = NULL;
  58 
  59     if (buffer == NULL) {
  60         buffer = (char*)malloc(buf_len * sizeof (char));
  61     }
  62 
  63     KeySym keysym;
  64     Status status;
  65     XKeyPressedEvent xevent = convert_event(event);
  66     if (XFilterEvent((XEvent*) & xevent, GDK_WINDOW_XID(gdk_window))) {
  67         return TRUE;
  68     }
  69 
  70     if (event->type == GDK_KEY_RELEASE) {
  71         process_key(event);
  72         return TRUE;
  73     }
  74 
  75     int len = Xutf8LookupString(xim.ic, &xevent, buffer, buf_len - 1, &keysym, &status);
  76     if (status == XBufferOverflow) {
  77         buf_len = len + 1;
  78         buffer = (char*)realloc(buffer, buf_len * sizeof (char));
  79         len = Xutf8LookupString(xim.ic, &xevent, buffer, buf_len - 1,
  80                 &keysym, &status);
  81     }
  82     switch (status) {
  83         case XLookupKeySym:
  84         case XLookupBoth:
  85             if (xevent.keycode) {
  86                 //process it as a normal key
  87                 process_key(event);
  88                 break;
  89             }
  90             // fall-through
  91         case XLookupChars:
  92             buffer[len] = 0;
  93             jstring str = mainEnv->NewStringUTF(buffer);
  94             EXCEPTION_OCCURED(mainEnv);
  95             jsize slen = mainEnv->GetStringLength(str);
  96             mainEnv->CallVoidMethod(jview,
  97                     jViewNotifyInputMethod,
  98                     str,
  99                     NULL, NULL, NULL,
 100                     slen,
 101                     slen,
 102                     0);
 103             LOG_EXCEPTION(mainEnv)
 104 
 105             break;
 106     }
 107 
 108     return TRUE;
 109 }
 110 
 111 bool WindowContextBase::filterIME(GdkEvent * event) {
 112     if (!hasIME()) {
 113         return false;
 114     }
 115 
 116     switch (event->type) {
 117         case GDK_KEY_PRESS:
 118         case GDK_KEY_RELEASE:
 119             return im_filter_keypress(reinterpret_cast<GdkEventKey*> (event));
 120         default:
 121             return FALSE;
 122     }
 123 }
 124 
 125 //Note: this function must return int, despite the fact it doesn't conform to XIMProc type.
 126 // This is required in documentation of XIM
 127 static int im_preedit_start(XIM im_xim, XPointer client, XPointer call) {
 128     (void)im_xim;
 129     (void)call;
 130 
 131     mainEnv->CallVoidMethod((jobject) client, jViewNotifyPreeditMode, JNI_TRUE);
 132     CHECK_JNI_EXCEPTION_RET(mainEnv, -1);
 133     return -1; // No restrictions
 134 }
 135 
 136 static void im_preedit_done(XIM im_xim, XPointer client, XPointer call) {
 137     (void)im_xim;
 138     (void)call;
 139 
 140     mainEnv->CallVoidMethod((jobject) client, jViewNotifyPreeditMode, JNI_FALSE);
 141     CHECK_JNI_EXCEPTION(mainEnv);
 142 }
 143 
 144 static void im_preedit_draw(XIM im_xim, XPointer client, XPointer call) {
 145     (void)im_xim;
 146     (void)call;
 147 
 148     XIMPreeditDrawCallbackStruct *data = (XIMPreeditDrawCallbackStruct*) call;
 149     jstring text = NULL;
 150     jbyteArray attr = NULL;
 151 
 152     if (data->text != NULL) {
 153         if (data->text->string.multi_byte) {
 154             if (data->text->encoding_is_wchar) {
 155                 size_t csize = wcstombs(NULL, data->text->string.wide_char, 0);
 156                 char *ctext = new char[csize + 1];
 157                 wcstombs(ctext, data->text->string.wide_char, csize + 1);
 158                 text = mainEnv->NewStringUTF(ctext);
 159                 delete[] ctext;
 160                 CHECK_JNI_EXCEPTION(mainEnv);
 161             } else {
 162                 text = mainEnv->NewStringUTF(data->text->string.multi_byte);
 163                 CHECK_JNI_EXCEPTION(mainEnv);
 164             }
 165         }
 166 
 167         if (XIMFeedback* fb = data->text->feedback) {
 168             attr = mainEnv->NewByteArray(data->text->length);
 169             CHECK_JNI_EXCEPTION(mainEnv)
 170             jbyte v[data->text->length];
 171             for (int i = 0; i < data->text->length; i++) {
 172                 if (fb[i] & XIMReverse) {
 173                     v[i] = com_sun_glass_ui_View_IME_ATTR_TARGET_NOTCONVERTED;
 174                 } else if (fb[i] & XIMHighlight) {
 175                     v[i] = com_sun_glass_ui_View_IME_ATTR_TARGET_CONVERTED;
 176                 } else if (fb[i] & XIMUnderline) {
 177                     v[i] = com_sun_glass_ui_View_IME_ATTR_CONVERTED;
 178                 } else {
 179                     v[i] = com_sun_glass_ui_View_IME_ATTR_INPUT;
 180                 }
 181             }
 182             mainEnv->SetByteArrayRegion(attr, 0, data->text->length, v);
 183             CHECK_JNI_EXCEPTION(mainEnv)
 184         }
 185     }
 186 
 187     mainEnv->CallVoidMethod((jobject)client, jViewNotifyInputMethodDraw,
 188             text, data->chg_first, data->chg_length, data->caret, attr);
 189     CHECK_JNI_EXCEPTION(mainEnv)
 190 }
 191 
 192 static void im_preedit_caret(XIM im_xim, XPointer client, XPointer call) {
 193     (void)im_xim;
 194 
 195     XIMPreeditCaretCallbackStruct *data = (XIMPreeditCaretCallbackStruct*) call;
 196     mainEnv->CallVoidMethod((jobject)client, jViewNotifyInputMethodCaret,
 197             data->position, data->direction, data->style);
 198     CHECK_JNI_EXCEPTION(mainEnv)
 199 }
 200 
 201 static XIMStyle get_best_supported_style(XIM im_xim)
 202 {
 203     XIMStyles* styles;
 204     int i;
 205     XIMStyle result = 0;
 206 
 207     if (XGetIMValues(im_xim, XNQueryInputStyle, &styles, NULL) != NULL) { // NULL means it's OK
 208         return 0;
 209     }
 210 
 211     for (i = 0; i < styles->count_styles; ++i) {
 212         if (styles->supported_styles[i] == (XIMPreeditCallbacks | XIMStatusNothing)
 213                 || styles->supported_styles[i] == (XIMPreeditNothing | XIMStatusNothing)) {
 214             result = styles->supported_styles[i];
 215             break;
 216         }
 217     }
 218 
 219     XFree(styles);
 220 
 221     return result;
 222 }
 223 
 224 void WindowContextBase::enableOrResetIME() {
 225     Display *display = gdk_x11_display_get_xdisplay(glass_gdk_window_get_display(gdk_window));
 226     if (xim.im == NULL || xim.ic == NULL) {
 227         xim.im = XOpenIM(display, NULL, NULL, NULL);
 228         if (xim.im == NULL) {
 229             return;
 230         }
 231 
 232         XIMStyle styles = get_best_supported_style(xim.im);
 233         if (styles == 0) {
 234             return;
 235         }
 236 
 237         XIMCallback startCallback = {(XPointer) jview, (XIMProc) im_preedit_start};
 238         XIMCallback doneCallback = {(XPointer) jview, im_preedit_done};
 239         XIMCallback drawCallback = {(XPointer) jview, im_preedit_draw};
 240         XIMCallback caretCallback = {(XPointer) jview, im_preedit_caret};
 241 
 242         XVaNestedList list = XVaCreateNestedList(0,
 243                 XNPreeditStartCallback, &startCallback,
 244                 XNPreeditDoneCallback, &doneCallback,
 245                 XNPreeditDrawCallback, &drawCallback,
 246                 XNPreeditCaretCallback, &caretCallback,
 247                 NULL);
 248 
 249         xim.ic = XCreateIC(xim.im,
 250                 XNInputStyle, styles,
 251                 XNClientWindow, GDK_WINDOW_XID(gdk_window),
 252                 XNPreeditAttributes, list,
 253                 NULL);
 254 
 255         XFree(list);
 256 
 257         if (xim.ic == NULL) {
 258             return;
 259         }
 260     }
 261 
 262     if (xim.enabled) { //called when changed focus to different input
 263         XmbResetIC(xim.ic);
 264     }
 265 
 266 
 267     XSetICFocus(xim.ic);
 268 
 269     xim.enabled = TRUE;
 270 }
 271 
 272 void WindowContextBase::disableIME() {
 273     if (xim.ic != NULL) {
 274         XUnsetICFocus(xim.ic);
 275     }
 276 }