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