Path: blob/master/src/java.desktop/unix/classes/sun/awt/X11InputMethodBase.java
41152 views
/*1* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.awt;2627import java.awt.AWTEvent;28import java.awt.AWTException;29import java.awt.Component;30import java.awt.Container;31import java.awt.EventQueue;32import java.awt.Window;33import java.awt.event.InputMethodEvent;34import java.awt.font.TextAttribute;35import java.awt.font.TextHitInfo;36import java.awt.im.InputMethodHighlight;37import java.awt.im.spi.InputMethodContext;38import java.awt.peer.ComponentPeer;39import java.io.BufferedReader;40import java.io.File;41import java.io.FileReader;42import java.io.IOException;43import java.lang.Character.Subset;44import java.lang.ref.WeakReference;45import java.text.AttributedCharacterIterator;46import java.text.AttributedString;47import java.util.Collections;48import java.util.HashMap;49import java.util.Locale;50import java.util.Map;51import java.util.StringTokenizer;52import java.util.regex.Pattern;5354import sun.awt.im.InputMethodAdapter;55import sun.util.logging.PlatformLogger;5657/**58* Input Method Adapter for XIM59*60* @author JavaSoft International61*/62public abstract class X11InputMethodBase extends InputMethodAdapter {63protected static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11InputMethod");64/*65* The following XIM* values must be the same as those defined in66* Xlib.h67*/68private static final int XIMReverse = (1<<0);69private static final int XIMUnderline = (1<<1);70private static final int XIMHighlight = (1<<2);71private static final int XIMPrimary = (1<<5);72private static final int XIMSecondary = (1<<6);73private static final int XIMTertiary = (1<<7);7475/*76* visible position values77*/78protected static final int XIMVisibleToForward = (1<<8);79protected static final int XIMVisibleToBackward = (1<<9);80protected static final int XIMVisibleCenter = (1<<10);81protected static final int XIMVisibleMask =82(XIMVisibleToForward | XIMVisibleToBackward | XIMVisibleCenter);8384private Locale locale;85private static boolean isXIMOpened = false;86protected Container clientComponentWindow = null;87protected Component awtFocussedComponent = null;88protected Component lastXICFocussedComponent = null;89protected boolean isLastXICActive = false;90protected boolean isLastTemporary = false;91protected boolean isActive = false;92private static Map<TextAttribute, ?>[] highlightStyles;93protected boolean disposed = false;9495//reset the XIC if necessary96protected boolean needResetXIC = false;97private WeakReference<Component> needResetXICClient = new WeakReference<>(null);9899// The use of compositionEnableSupported is to reduce unnecessary100// native calls if set/isCompositionEnabled101// throws UnsupportedOperationException.102// It is set to false if that exception is thrown first time103// either of the two methods are called.104protected boolean compositionEnableSupported = true;105// The savedCompositionState indicates the composition mode when106// endComposition or setCompositionEnabled is called. It doesn't always107// reflect the actual composition state because it doesn't get updated108// when the user changes the composition state through direct interaction109// with the input method. It is used to save the composition mode when110// focus is traversed across different client components sharing the111// same java input context. Also if set/isCompositionEnabled are not112// supported, it remains false.113protected boolean savedCompositionState = false;114115// variables to keep track of preedit context.116// these variables need to be accessed within AWT_LOCK/UNLOCK117protected String committedText = null;118protected StringBuffer composedText = null;119protected IntBuffer rawFeedbacks;120121// private data (X11InputMethodData structure defined in122// awt_InputMethod.c) for native methods123// this structure needs to be accessed within AWT_LOCK/UNLOCK124protected transient long pData = 0; // accessed by native125126// Initialize highlight mapping table127static {128@SuppressWarnings({"unchecked", "rawtypes"})129Map<TextAttribute, ?>[] styles = new Map[4];130HashMap<TextAttribute, Object> map;131132// UNSELECTED_RAW_TEXT_HIGHLIGHT133map = new HashMap<>(1);134map.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);135styles[0] = Collections.unmodifiableMap(map);136137// SELECTED_RAW_TEXT_HIGHLIGHT138map = new HashMap<>(1);139map.put(TextAttribute.SWAP_COLORS, TextAttribute.SWAP_COLORS_ON);140styles[1] = Collections.unmodifiableMap(map);141142// UNSELECTED_CONVERTED_TEXT_HIGHLIGHT143map = new HashMap<>(1);144map.put(TextAttribute.INPUT_METHOD_UNDERLINE,145TextAttribute.UNDERLINE_LOW_ONE_PIXEL);146styles[2] = Collections.unmodifiableMap(map);147148// SELECTED_CONVERTED_TEXT_HIGHLIGHT149map = new HashMap<>(1);150map.put(TextAttribute.SWAP_COLORS, TextAttribute.SWAP_COLORS_ON);151styles[3] = Collections.unmodifiableMap(map);152153highlightStyles = styles;154}155156static {157initIDs();158}159160/**161* Constructs an X11InputMethod instance. It initializes the XIM162* environment if it's not done yet.163*164* @exception AWTException if XOpenIM() failed.165*/166public X11InputMethodBase() throws AWTException {167// supports only the locale in which the VM is started168locale = X11InputMethodDescriptor.getSupportedLocale();169if (initXIM() == false) {170throw new AWTException("Cannot open X Input Method");171}172}173174@SuppressWarnings("deprecation")175protected void finalize() throws Throwable {176dispose();177super.finalize();178}179180/**181* Invokes openIM() that invokes XOpenIM() if it's not opened yet.182* @return true if openXIM() is successful or it's already been opened.183*/184private synchronized boolean initXIM() {185if (isXIMOpened == false)186isXIMOpened = openXIM();187return isXIMOpened;188}189190protected abstract boolean openXIM();191192protected boolean isDisposed() {193return disposed;194}195196protected abstract void setXICFocus(ComponentPeer peer,197boolean value, boolean active);198199/**200* Does nothing - this adapter doesn't use the input method context.201*202* @see java.awt.im.spi.InputMethod#setInputMethodContext203*/204public void setInputMethodContext(InputMethodContext context) {205}206207/**208* Set locale to input. If input method doesn't support specified locale,209* false will be returned and its behavior is not changed.210*211* @param lang locale to input212* @return the true is returned when specified locale is supported.213*/214public boolean setLocale(Locale lang) {215if (lang.equals(locale)) {216return true;217}218// special compatibility rule for Japanese and Korean219if (locale.equals(Locale.JAPAN) && lang.equals(Locale.JAPANESE) ||220locale.equals(Locale.KOREA) && lang.equals(Locale.KOREAN)) {221return true;222}223return false;224}225226/**227* Returns current input locale.228*/229public Locale getLocale() {230return locale;231}232233/**234* Does nothing - XIM doesn't let you specify which characters you expect.235*236* @see java.awt.im.spi.InputMethod#setCharacterSubsets237*/238public void setCharacterSubsets(Subset[] subsets) {239}240241/**242* Dispatch event to input method. InputContext dispatch event with this243* method. Input method set consume flag if event is consumed in244* input method.245*246* @param e event247*/248public void dispatchEvent(AWTEvent e) {249}250251protected final void resetXICifneeded(){252/* needResetXIC is used to indicate whether to call253resetXIC on the active client. resetXIC will always be254called on the passive client when endComposition is called.255*/256if (needResetXIC && haveActiveClient() &&257getClientComponent() != needResetXICClient.get()){258resetXIC();259260// needs to reset the last xic focussed component.261lastXICFocussedComponent = null;262isLastXICActive = false;263264needResetXICClient.clear();265needResetXIC = false;266}267}268269/**270* Reset the composition state to the current composition state.271*/272protected abstract void resetCompositionState();273274/**275* Query and then return the current composition state.276* @return the composition state if isCompositionEnabled call277* is successful. Otherwise, it returns false.278*/279protected boolean getCompositionState() {280boolean compositionState = false;281if (compositionEnableSupported) {282try {283compositionState = isCompositionEnabled();284} catch (UnsupportedOperationException e) {285compositionEnableSupported = false;286}287}288return compositionState;289}290291/**292* Activate input method.293*/294public abstract void activate();295296protected abstract boolean createXIC();297298/**299* Deactivate input method.300*/301public abstract void deactivate(boolean isTemporary);302303/**304* Explicitly disable the native IME. Native IME is not disabled when305* deactivate is called.306*/307public void disableInputMethod() {308if (lastXICFocussedComponent != null) {309setXICFocus(getPeer(lastXICFocussedComponent), false, isLastXICActive);310lastXICFocussedComponent = null;311isLastXICActive = false;312313resetXIC();314needResetXICClient.clear();315needResetXIC = false;316}317}318319// implements java.awt.im.spi.InputMethod.hideWindows320public abstract void hideWindows();321322/**323* @see java.awt.Toolkit#mapInputMethodHighlight324*/325public static Map<TextAttribute, ?> mapInputMethodHighlight(InputMethodHighlight highlight) {326int index;327int state = highlight.getState();328if (state == InputMethodHighlight.RAW_TEXT) {329index = 0;330} else if (state == InputMethodHighlight.CONVERTED_TEXT) {331index = 2;332} else {333return null;334}335if (highlight.isSelected()) {336index += 1;337}338return highlightStyles[index];339}340341/**342* @see sun.awt.im.InputMethodAdapter#setAWTFocussedComponent343*/344protected void setAWTFocussedComponent(Component component) {345if (component == null) {346return;347}348if (isActive) {349// deactivate/activate are being suppressed during a focus change -350// this may happen when an input method window is made visible351boolean ac = haveActiveClient();352setXICFocus(getPeer(awtFocussedComponent), false, ac);353setXICFocus(getPeer(component), true, ac);354}355awtFocussedComponent = component;356}357358/**359* @see sun.awt.im.InputMethodAdapter#stopListening360*/361protected void stopListening() {362// It is desirable to disable XIM by calling XSetICValues with363// XNPreeditState == XIMPreeditDisable. But Solaris 2.6 and364// Solaris 7 do not implement this correctly without a patch,365// so just call resetXIC here. Prior endComposition call commits366// the existing composed text.367endComposition();368// disable the native input method so that the other input369// method could get the input focus.370disableInputMethod();371if (needResetXIC) {372resetXIC();373needResetXICClient.clear();374needResetXIC = false;375}376}377378/**379* Returns the Window instance in which the client component is380* contained. If not found, null is returned. (IS THIS POSSIBLE?)381*/382// NOTE: This method may be called by privileged threads.383// DO NOT INVOKE CLIENT CODE ON THIS THREAD!384protected Window getClientComponentWindow() {385Component client = getClientComponent();386Container container;387388if (client instanceof Container) {389container = (Container) client;390} else {391container = getParent(client);392}393394while (container != null && !(container instanceof java.awt.Window)) {395container = getParent(container);396}397return (Window) container;398}399400protected abstract Container getParent(Component client);401402/**403* Returns peer of the given client component. If the given client component404* doesn't have peer, peer of the native container of the client is returned.405*/406protected abstract ComponentPeer getPeer(Component client);407408/**409* Used to protect preedit data410*/411protected abstract void awtLock();412protected abstract void awtUnlock();413414/**415* Creates an input method event from the arguments given416* and posts it on the AWT event queue. For arguments,417* see InputMethodEvent. Called by input method.418*419* @see java.awt.event.InputMethodEvent#InputMethodEvent420*/421protected void postInputMethodEvent(int id,422AttributedCharacterIterator text,423int committedCharacterCount,424TextHitInfo caret,425TextHitInfo visiblePosition,426long when) {427Component source = getClientComponent();428if (source != null) {429InputMethodEvent event = new InputMethodEvent(source,430id, when, text, committedCharacterCount, caret, visiblePosition);431SunToolkit.postEvent(SunToolkit.targetToAppContext(source), (AWTEvent)event);432}433}434435private void postInputMethodEvent(int id,436AttributedCharacterIterator text,437int committedCharacterCount,438TextHitInfo caret,439TextHitInfo visiblePosition) {440postInputMethodEvent(id, text, committedCharacterCount,441caret, visiblePosition, EventQueue.getMostRecentEventTime());442}443444/**445* Dispatches committed text from XIM to the awt event queue. This446* method is invoked from the event handler in canvas.c in the447* AWT Toolkit thread context and thus inside the AWT Lock.448* @param str committed text449* @param when when450*/451// NOTE: This method may be called by privileged threads.452// This functionality is implemented in a package-private method453// to insure that it cannot be overridden by client subclasses.454// DO NOT INVOKE CLIENT CODE ON THIS THREAD!455void dispatchCommittedText(String str, long when) {456if (str == null)457return;458459if (composedText == null) {460AttributedString attrstr = new AttributedString(str);461postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,462attrstr.getIterator(),463str.length(),464null,465null,466when);467} else {468// if there is composed text, wait until the preedit469// callback is invoked.470committedText = str;471}472}473474private void dispatchCommittedText(String str) {475dispatchCommittedText(str, EventQueue.getMostRecentEventTime());476}477478/**479* Updates composed text with XIM preedit information and480* posts composed text to the awt event queue. The args of481* this method correspond to the XIM preedit callback482* information. The XIM highlight attributes are translated via483* fixed mapping (i.e., independent from any underlying input484* method engine). This method is invoked in the AWT Toolkit485* (X event loop) thread context and thus inside the AWT Lock.486*/487// NOTE: This method may be called by privileged threads.488// This functionality is implemented in a package-private method489// to insure that it cannot be overridden by client subclasses.490// DO NOT INVOKE CLIENT CODE ON THIS THREAD!491abstract void dispatchComposedText(String chgText,492int[] chgStyles,493int chgOffset,494int chgLength,495int caretPosition,496long when);497498/**499* Flushes composed and committed text held in this context.500* This method is invoked in the AWT Toolkit (X event loop) thread context501* and thus inside the AWT Lock.502*/503// NOTE: This method may be called by privileged threads.504// This functionality is implemented in a package-private method505// to insure that it cannot be overridden by client subclasses.506// DO NOT INVOKE CLIENT CODE ON THIS THREAD!507void flushText() {508String flush = (committedText != null ? committedText : "");509if (composedText != null) {510flush += composedText.toString();511}512513if (!flush.isEmpty()) {514AttributedString attrstr = new AttributedString(flush);515postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,516attrstr.getIterator(),517flush.length(),518null,519null,520EventQueue.getMostRecentEventTime());521composedText = null;522committedText = null;523}524}525526/*527* Subclasses should override disposeImpl() instead of dispose(). Client528* code should always invoke dispose(), never disposeImpl().529*/530protected abstract void disposeImpl();531532/**533* Frees all X Window resources associated with this object.534*535* @see java.awt.im.spi.InputMethod#dispose536*/537public final void dispose() {538boolean call_disposeImpl = false;539540if (!disposed) {541synchronized (this) {542if (!disposed) {543disposed = call_disposeImpl = true;544}545}546}547548if (call_disposeImpl) {549disposeImpl();550}551}552553/**554* Returns null.555*556* @see java.awt.im.spi.InputMethod#getControlObject557*/558public Object getControlObject() {559return null;560}561562/**563* @see java.awt.im.spi.InputMethod#removeNotify564*/565public synchronized void removeNotify() {566dispose();567}568569/**570* @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean)571*/572public abstract void setCompositionEnabled(boolean enable);573574/**575* @see java.awt.im.spi.InputMethod#isCompositionEnabled576*/577public boolean isCompositionEnabled() {578/* isCompositionEnabledNative may throw UnsupportedOperationException.579Don't try to catch it since this method may be called by clients.580Use package private method 'getCompositionState' if you want the581exception to be caught.582*/583return isCompositionEnabledNative();584}585586/**587* Ends any input composition that may currently be going on in this588* context. Depending on the platform and possibly user preferences,589* this may commit or delete uncommitted text. Any changes to the text590* are communicated to the active component using an input method event.591*592* <p>593* A text editing component may call this in a variety of situations,594* for example, when the user moves the insertion point within the text595* (but outside the composed text), or when the component's text is596* saved to a file or copied to the clipboard.597*598*/599public void endComposition() {600if (disposed) {601return;602}603604/* Before calling resetXIC, record the current composition mode605so that it can be restored later. */606savedCompositionState = getCompositionState();607boolean active = haveActiveClient();608if (active && composedText == null && committedText == null){609needResetXIC = true;610needResetXICClient = new WeakReference<>(getClientComponent());611return;612}613614String text = resetXIC();615/* needResetXIC is only set to true for active client. So passive616client should not reset the flag to false. */617if (active) {618needResetXIC = false;619}620621// Remove any existing composed text by posting an InputMethodEvent622// with null composed text. It would be desirable to wait for a623// dispatchComposedText call from X input method engine, but some624// input method does not conform to the XIM specification and does625// not call the preedit callback to erase preedit text on calling626// XmbResetIC. To work around this problem, do it here by ourselves.627awtLock();628try {629composedText = null;630postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,631null,6320,633null,634null);635636if (text != null && text.length() > 0) {637dispatchCommittedText(text);638}639} finally {640// Put awtUnlock into finally block in case an exception is thrown.641awtUnlock();642}643644// Restore the preedit state if it was enabled645if (savedCompositionState) {646resetCompositionState();647}648}649650/**651* Returns a string with information about the current input method server, or null.652* On both Linux & SunOS, the value of environment variable XMODIFIERS is653* returned if set. Otherwise, on SunOS, $HOME/.dtprofile will be parsed654* to find out the language service engine (atok or wnn) since there is655* no API in Xlib which returns the information of native656* IM server or language service and we want to try our best to return as much657* information as possible.658*659* Note: This method could return null on Linux if XMODIFIERS is not set properly or660* if any IOException is thrown.661* See man page of XSetLocaleModifiers(3X11) for the usgae of XMODIFIERS,662* atok12setup(1) and wnn6setup(1) for the information written to663* $HOME/.dtprofile when you run these two commands.664*665*/666public String getNativeInputMethodInfo() {667String xmodifiers = System.getenv("XMODIFIERS");668String imInfo = null;669670// If XMODIFIERS is set, return the value671if (xmodifiers != null) {672int imIndex = xmodifiers.indexOf("@im=");673if (imIndex != -1) {674imInfo = xmodifiers.substring(imIndex + 4);675}676}677678return imInfo;679}680681682/**683* Performs mapping from an XIM visible feedback value to Java IM highlight.684* @return Java input method highlight685*/686protected InputMethodHighlight convertVisualFeedbackToHighlight(int feedback) {687InputMethodHighlight highlight;688689switch (feedback) {690case XIMUnderline:691highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT;692break;693case XIMReverse:694highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT;695break;696case XIMHighlight:697highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;698break;699case 0: //None of the values are set by Wnn700case XIMPrimary:701highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT;702break;703case XIMSecondary:704highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT;705break;706case XIMTertiary:707highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;708break;709default:710highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;711break;712}713return highlight;714}715716// initial capacity size for string buffer, etc.717protected static final int INITIAL_SIZE = 64;718719/**720* IntBuffer is an inner class that manipulates an int array and721* provides UNIX file io stream-like programming interfaces to722* access it. (An alternative would be to use ArrayList which may723* be too expensive for the work.)724*/725protected final class IntBuffer {726private int[] intArray;727private int size;728private int index;729730IntBuffer(int initialCapacity) {731intArray = new int[initialCapacity];732size = 0;733index = 0;734}735736void insert(int offset, int[] values) {737int newSize = size + values.length;738if (intArray.length < newSize) {739int[] newIntArray = new int[newSize * 2];740System.arraycopy(intArray, 0, newIntArray, 0, size);741intArray = newIntArray;742}743System.arraycopy(intArray, offset, intArray, offset+values.length,744size - offset);745System.arraycopy(values, 0, intArray, offset, values.length);746size += values.length;747if (index > offset)748index = offset;749}750751void remove(int offset, int length) {752if (offset + length != size)753System.arraycopy(intArray, offset+length, intArray, offset,754size - offset - length);755size -= length;756if (index > offset)757index = offset;758}759760void replace(int offset, int[] values) {761System.arraycopy(values, 0, intArray, offset, values.length);762}763764void removeAll() {765size = 0;766index = 0;767}768769void rewind() {770index = 0;771}772773int getNext() {774if (index == size)775return -1;776return intArray[index++];777}778779void unget() {780if (index != 0)781index--;782}783784int getOffset() {785return index;786}787788public String toString() {789StringBuilder s = new StringBuilder();790for (int i = 0; i < size;) {791s.append(intArray[i++]);792if (i < size)793s.append(",");794}795return s.toString();796}797}798799/*800* Native methods801*/802803/**804* Initialize JNI field and method IDs for fields that may be805* accessed from C.806*/807private static native void initIDs();808809protected native void turnoffStatusWindow();810811protected native void disposeXIC();812813private native String resetXIC();814815protected native boolean setCompositionEnabledNative(boolean enable);816817private native boolean isCompositionEnabledNative();818}819820821