| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_vcl.hxx" |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| #ifdef LINUX |
| # ifndef __USE_XOPEN |
| # define __USE_XOPEN |
| # endif |
| #endif |
| #include <poll.h> |
| |
| #include <tools/prex.h> |
| #include <X11/Xlocale.h> |
| #include <X11/Xlib.h> |
| #include <unx/XIM.h> |
| #include <tools/postx.h> |
| |
| #include "unx/salunx.h" |
| #include "unx/saldisp.hxx" |
| #include "unx/i18n_im.hxx" |
| #include "unx/i18n_status.hxx" |
| |
| #include <osl/thread.h> |
| #include <osl/process.h> |
| |
| using namespace vcl; |
| #include "unx/i18n_cb.hxx" |
| #if defined(SOLARIS) || defined(LINUX) |
| extern "C" char * XSetIMValues(XIM im, ...); |
| #endif |
| |
| // ------------------------------------------------------------------------------------ |
| // |
| // kinput2 IME needs special key handling since key release events are filtered in |
| // preeditmode and XmbResetIC does not work |
| // |
| // ------------------------------------------------------------------------------------ |
| |
| Bool |
| IMServerKinput2 () |
| { |
| const static char* p_xmodifiers = getenv ("XMODIFIERS"); |
| const static Bool b_kinput2 = (p_xmodifiers != NULL) |
| && (strcmp(p_xmodifiers, "@im=kinput2") == 0); |
| |
| return b_kinput2; |
| } |
| |
| class XKeyEventOp : XKeyEvent |
| { |
| private: |
| void init(); |
| |
| public: |
| XKeyEventOp(); |
| ~XKeyEventOp(); |
| |
| XKeyEventOp& operator= (const XKeyEvent &rEvent); |
| void erase (); |
| Bool match (const XKeyEvent &rEvent) const; |
| }; |
| |
| void |
| XKeyEventOp::init() |
| { |
| type = 0; /* serial = 0; */ |
| send_event = 0; display = 0; |
| window = 0; root = 0; |
| subwindow = 0; /* time = 0; */ |
| /* x = 0; y = 0; */ |
| /* x_root = 0; y_root = 0; */ |
| state = 0; keycode = 0; |
| same_screen = 0; |
| } |
| |
| XKeyEventOp::XKeyEventOp() |
| { |
| init(); |
| } |
| |
| XKeyEventOp::~XKeyEventOp() |
| { |
| } |
| |
| XKeyEventOp& |
| XKeyEventOp::operator= (const XKeyEvent &rEvent) |
| { |
| type = rEvent.type; /* serial = rEvent.serial; */ |
| send_event = rEvent.send_event; display = rEvent.display; |
| window = rEvent.window; root = rEvent.root; |
| subwindow = rEvent.subwindow;/* time = rEvent.time; */ |
| /* x = rEvent.x, y = rEvent.y; */ |
| /* x_root = rEvent.x_root, y_root = rEvent.y_root; */ |
| state = rEvent.state; keycode = rEvent.keycode; |
| same_screen = rEvent.same_screen; |
| |
| return *this; |
| } |
| |
| void |
| XKeyEventOp::erase () |
| { |
| init(); |
| } |
| |
| Bool |
| XKeyEventOp::match (const XKeyEvent &rEvent) const |
| { |
| return ( (type == XLIB_KeyPress && rEvent.type == KeyRelease) |
| || (type == KeyRelease && rEvent.type == XLIB_KeyPress )) |
| /* && serial == rEvent.serial */ |
| && send_event == rEvent.send_event |
| && display == rEvent.display |
| && window == rEvent.window |
| && root == rEvent.root |
| && subwindow == rEvent.subwindow |
| /* && time == rEvent.time |
| && x == rEvent.x |
| && y == rEvent.y |
| && x_root == rEvent.x_root |
| && y_root == rEvent.y_root */ |
| && state == rEvent.state |
| && keycode == rEvent.keycode |
| && same_screen == rEvent.same_screen; |
| } |
| |
| // ------------------------------------------------------------------------- |
| // |
| // locale handling |
| // |
| // ------------------------------------------------------------------------- |
| |
| // Locale handling of the operating system layer |
| |
| static char* |
| SetSystemLocale( const char* p_inlocale ) |
| { |
| char *p_outlocale; |
| |
| if ( (p_outlocale = setlocale(LC_ALL, p_inlocale)) == NULL ) |
| { |
| fprintf( stderr, "I18N: Operating system doesn't support locale \"%s\"\n", |
| p_inlocale ); |
| } |
| |
| return p_outlocale; |
| } |
| |
| #ifdef SOLARIS |
| static void |
| SetSystemEnvironment( const rtl::OUString& rLocale ) |
| { |
| rtl::OUString LC_ALL_Var(RTL_CONSTASCII_USTRINGPARAM("LC_ALL")); |
| osl_setEnvironment(LC_ALL_Var.pData, rLocale.pData); |
| |
| rtl::OUString LANG_Var(RTL_CONSTASCII_USTRINGPARAM("LANG")); |
| osl_setEnvironment(LANG_Var.pData, rLocale.pData); |
| } |
| #endif |
| |
| static Bool |
| IsPosixLocale( const char* p_locale ) |
| { |
| if ( p_locale == NULL ) |
| return False; |
| if ( (p_locale[ 0 ] == 'C') && (p_locale[ 1 ] == '\0') ) |
| return True; |
| if ( strncmp(p_locale, "POSIX", sizeof("POSIX")) == 0 ) |
| return True; |
| |
| return False; |
| } |
| |
| // Locale handling of the X Window System layer |
| |
| static Bool |
| IsXWindowCompatibleLocale( const char* p_locale ) |
| { |
| if ( p_locale == NULL ) |
| return False; |
| |
| if ( !XSupportsLocale() ) |
| { |
| fprintf (stderr, "I18N: X Window System doesn't support locale \"%s\"\n", |
| p_locale ); |
| return False; |
| } |
| return True; |
| } |
| |
| // Set the operating system locale prior to trying to open an |
| // XIM InputMethod. |
| // Handle the cases where the current locale is either not supported by the |
| // operating system (LANG=gaga) or by the XWindow system (LANG=aa_ER@saaho) |
| // by providing a fallback. |
| // Upgrade "C" or "POSIX" to "en_US" locale to allow umlauts and accents |
| // see i8988, i9188, i8930, i16318 |
| // on Solaris the environment needs to be set equivalent to the locale (#i37047#) |
| |
| Bool |
| SalI18N_InputMethod::SetLocale( const char* pLocale ) |
| { |
| // check whether we want an Input Method engine, if we don't we |
| // do not need to set the locale |
| if ( mbUseable ) |
| { |
| char *locale = SetSystemLocale( pLocale ); |
| if ( (!IsXWindowCompatibleLocale(locale)) || IsPosixLocale(locale) ) |
| { |
| osl_setThreadTextEncoding (RTL_TEXTENCODING_ISO_8859_1); |
| locale = SetSystemLocale( "en_US" ); |
| #ifdef SOLARIS |
| SetSystemEnvironment( rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("en_US")) ); |
| #endif |
| if (! IsXWindowCompatibleLocale(locale)) |
| { |
| locale = SetSystemLocale( "C" ); |
| #ifdef SOLARIS |
| SetSystemEnvironment( rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("C")) ); |
| #endif |
| if (! IsXWindowCompatibleLocale(locale)) |
| mbUseable = False; |
| } |
| } |
| |
| // must not fail if mbUseable since XSupportsLocale() asserts success |
| if ( mbUseable && XSetLocaleModifiers("") == NULL ) |
| { |
| fprintf (stderr, "I18N: Can't set X modifiers for locale \"%s\"\n", |
| locale); |
| mbUseable = False; |
| } |
| } |
| |
| return mbUseable; |
| } |
| |
| Bool |
| SalI18N_InputMethod::PosixLocale() |
| { |
| if (mbMultiLingual) |
| return False; |
| if (maMethod) |
| return IsPosixLocale (XLocaleOfIM (maMethod)); |
| return False; |
| } |
| |
| // ------------------------------------------------------------------------ |
| // |
| // Constructor / Destructor / Initialisation |
| // |
| // ------------------------------------------------------------------------ |
| |
| SalI18N_InputMethod::SalI18N_InputMethod( ) : mbUseable( bUseInputMethodDefault ), |
| mbMultiLingual( False ), |
| maMethod( (XIM)NULL ), |
| mpStyles( (XIMStyles*)NULL ) |
| { |
| const char *pUseInputMethod = getenv( "SAL_USEINPUTMETHOD" ); |
| if ( pUseInputMethod != NULL ) |
| mbUseable = pUseInputMethod[0] != '\0' ; |
| } |
| |
| SalI18N_InputMethod::~SalI18N_InputMethod() |
| { |
| ::vcl::I18NStatus::free(); |
| if ( mpStyles != NULL ) |
| XFree( mpStyles ); |
| if ( maMethod != NULL ) |
| XCloseIM ( maMethod ); |
| } |
| |
| // |
| // XXX |
| // debug routine: lets have a look at the provided method styles |
| // |
| |
| #if OSL_DEBUG_LEVEL > 1 |
| |
| extern "C" char* |
| GetMethodName( XIMStyle nStyle, char *pBuf, int nBufSize) |
| { |
| struct StyleName { |
| const XIMStyle nStyle; |
| const char *pName; |
| const int nNameLen; |
| }; |
| |
| StyleName *pDescPtr; |
| static const StyleName pDescription[] = { |
| { XIMPreeditArea, "PreeditArea ", sizeof("PreeditArea ") }, |
| { XIMPreeditCallbacks, "PreeditCallbacks ",sizeof("PreeditCallbacks ")}, |
| { XIMPreeditPosition, "PreeditPosition ", sizeof("PreeditPosition ") }, |
| { XIMPreeditNothing, "PreeditNothing ", sizeof("PreeditNothing ") }, |
| { XIMPreeditNone, "PreeditNone ", sizeof("PreeditNone ") }, |
| { XIMStatusArea, "StatusArea ", sizeof("StatusArea ") }, |
| { XIMStatusCallbacks, "StatusCallbacks ", sizeof("StatusCallbacks ") }, |
| { XIMStatusNothing, "StatusNothing ", sizeof("StatusNothing ") }, |
| { XIMStatusNone, "StatusNone ", sizeof("StatusNone ") }, |
| { 0, "NULL", 0 } |
| }; |
| |
| if ( nBufSize > 0 ) |
| pBuf[0] = '\0'; |
| |
| char *pBufPtr = pBuf; |
| for ( pDescPtr = const_cast<StyleName*>(pDescription); pDescPtr->nStyle != 0; pDescPtr++ ) |
| { |
| int nSize = pDescPtr->nNameLen - 1; |
| if ( (nStyle & pDescPtr->nStyle) && (nBufSize > nSize) ) |
| { |
| strncpy( pBufPtr, pDescPtr->pName, nSize + 1); |
| pBufPtr += nSize; |
| nBufSize -= nSize; |
| } |
| } |
| |
| return pBuf; |
| } |
| |
| extern "C" void |
| PrintInputStyle( XIMStyles *pStyle ) |
| { |
| char pBuf[ 128 ]; |
| int nBuf = sizeof( pBuf ); |
| |
| if ( pStyle == NULL ) |
| fprintf( stderr, "no input method styles\n"); |
| else |
| for ( int nStyle = 0; nStyle < pStyle->count_styles; nStyle++ ) |
| { |
| fprintf( stderr, "style #%i = %s\n", nStyle, |
| GetMethodName(pStyle->supported_styles[nStyle], pBuf, nBuf) ); |
| } |
| } |
| |
| #endif |
| |
| // |
| // this is the real constructing routine, since locale setting has to be done |
| // prior to xopendisplay, the xopenim call has to be delayed |
| // |
| |
| Bool |
| SalI18N_InputMethod::CreateMethod ( Display *pDisplay ) |
| { |
| if ( mbUseable ) |
| { |
| const bool bTryMultiLingual = |
| #ifdef LINUX |
| false; |
| #else |
| true; |
| #endif |
| if ( bTryMultiLingual && getenv("USE_XOPENIM") == NULL ) |
| { |
| mbMultiLingual = True; // set ml-input flag to create input-method |
| maMethod = XvaOpenIM(pDisplay, NULL, NULL, NULL, |
| XNMultiLingualInput, mbMultiLingual, /* dummy */ |
| (void *)0); |
| // get ml-input flag from input-method |
| if ( maMethod == (XIM)NULL ) |
| mbMultiLingual = False; |
| else |
| if ( XGetIMValues(maMethod, |
| XNMultiLingualInput, &mbMultiLingual, NULL ) != NULL ) |
| mbMultiLingual = False; |
| if( mbMultiLingual ) |
| { |
| XIMUnicodeCharacterSubsets* subsets; |
| if( XGetIMValues( maMethod, |
| XNQueryUnicodeCharacterSubset, &subsets, NULL ) == NULL ) |
| { |
| #if OSL_DEBUG_LEVEL > 1 |
| fprintf( stderr, "IM reports %d subsets: ", subsets->count_subsets ); |
| #endif |
| I18NStatus& rStatus( I18NStatus::get() ); |
| rStatus.clearChoices(); |
| for( int i = 0; i < subsets->count_subsets; i++ ) |
| { |
| #if OSL_DEBUG_LEVEL > 1 |
| fprintf( stderr,"\"%s\" ", subsets->supported_subsets[i].name ); |
| #endif |
| rStatus.addChoice( String( subsets->supported_subsets[i].name, RTL_TEXTENCODING_UTF8 ), &subsets->supported_subsets[i] ); |
| } |
| #if OSL_DEBUG_LEVEL > 1 |
| fprintf( stderr, "\n" ); |
| #endif |
| } |
| #if OSL_DEBUG_LEVEL > 1 |
| else |
| fprintf( stderr, "query subsets failed\n" ); |
| #endif |
| } |
| } |
| else |
| { |
| maMethod = XOpenIM(pDisplay, NULL, NULL, NULL); |
| mbMultiLingual = False; |
| } |
| |
| if ((maMethod == (XIM)NULL) && (getenv("XMODIFIERS") != NULL)) |
| { |
| rtl::OUString envVar(RTL_CONSTASCII_USTRINGPARAM("XMODIFIERS")); |
| osl_clearEnvironment(envVar.pData); |
| XSetLocaleModifiers(""); |
| maMethod = XOpenIM(pDisplay, NULL, NULL, NULL); |
| mbMultiLingual = False; |
| } |
| |
| if ( maMethod != (XIM)NULL ) |
| { |
| if ( XGetIMValues(maMethod, XNQueryInputStyle, &mpStyles, NULL) |
| != NULL) |
| mbUseable = False; |
| #if OSL_DEBUG_LEVEL > 1 |
| fprintf(stderr, "Creating %s-Lingual InputMethod\n", |
| mbMultiLingual ? "Multi" : "Mono" ); |
| PrintInputStyle( mpStyles ); |
| #endif |
| } |
| else |
| { |
| mbUseable = False; |
| } |
| } |
| |
| #if OSL_DEBUG_LEVEL > 1 |
| if ( !mbUseable ) |
| fprintf(stderr, "input method creation failed\n"); |
| #endif |
| |
| maDestroyCallback.callback = (XIMProc)IM_IMDestroyCallback; |
| maDestroyCallback.client_data = (XPointer)this; |
| if (mbUseable && maMethod != NULL) |
| XSetIMValues(maMethod, XNDestroyCallback, &maDestroyCallback, NULL); |
| |
| return mbUseable; |
| } |
| |
| // |
| // give IM the opportunity to look at the event, and possibly hide it |
| // |
| |
| Bool |
| SalI18N_InputMethod::FilterEvent( XEvent *pEvent, XLIB_Window window ) |
| { |
| if (!mbUseable) |
| return False; |
| |
| Bool bFilterEvent = XFilterEvent (pEvent, window); |
| |
| if (pEvent->type != XLIB_KeyPress && pEvent->type != KeyRelease) |
| return bFilterEvent; |
| |
| /* |
| * fix broken key release handling of some IMs |
| */ |
| XKeyEvent* pKeyEvent = &(pEvent->xkey); |
| static XKeyEventOp maLastKeyPress; |
| |
| if (bFilterEvent) |
| { |
| if (pKeyEvent->type == KeyRelease) |
| bFilterEvent = !maLastKeyPress.match (*pKeyEvent); |
| maLastKeyPress.erase(); |
| } |
| else /* (!bFilterEvent) */ |
| { |
| if (pKeyEvent->type == XLIB_KeyPress) |
| maLastKeyPress = *pKeyEvent; |
| else |
| maLastKeyPress.erase(); |
| } |
| |
| return bFilterEvent; |
| } |
| |
| void |
| SalI18N_InputMethod::HandleDestroyIM() |
| { |
| mbUseable = False; |
| mbMultiLingual = False; |
| maMethod = NULL; |
| } |
| |
| // ------------------------------------------------------------------------ |
| // |
| // add a connection watch into the SalXLib yieldTable to allow iiimp |
| // connection processing: soffice waits in select() not in XNextEvent(), so |
| // there may be requests pending on the iiimp internal connection that will |
| // not be processed until XNextEvent is called the next time. If we do not |
| // have the focus because the atok12 lookup choice aux window has it we stay |
| // deaf and dump otherwise. |
| // |
| // ------------------------------------------------------------------------ |
| |
| int |
| InputMethod_HasPendingEvent(int nFileDescriptor, void *pData) |
| { |
| if (pData == NULL) |
| return 0; |
| |
| struct pollfd aFileDescriptor; |
| #ifdef SOLARIS |
| nfds_t nNumDescriptor = 1; |
| #else |
| unsigned int nNumDescriptor = 1; |
| #endif |
| aFileDescriptor.fd = nFileDescriptor; |
| aFileDescriptor.events = POLLRDNORM; |
| aFileDescriptor.revents = 0; |
| |
| int nPoll = poll (&aFileDescriptor, nNumDescriptor, 0 /* timeout */ ); |
| |
| if (nPoll > 0) |
| { |
| /* at least some conditions in revent are set */ |
| if ( (aFileDescriptor.revents & POLLHUP) |
| || (aFileDescriptor.revents & POLLERR) |
| || (aFileDescriptor.revents & POLLNVAL)) |
| return 0; /* oops error condition set */ |
| |
| if (aFileDescriptor.revents & POLLRDNORM) |
| return 1; /* success */ |
| } |
| |
| /* nPoll == 0 means timeout, nPoll < 0 means error */ |
| return 0; |
| } |
| |
| int |
| InputMethod_IsEventQueued(int nFileDescriptor, void *pData) |
| { |
| return InputMethod_HasPendingEvent (nFileDescriptor, pData); |
| } |
| |
| int |
| InputMethod_HandleNextEvent(int nFileDescriptor, void *pData) |
| { |
| if (pData != NULL) |
| XProcessInternalConnection((Display*)pData, nFileDescriptor); |
| |
| return 0; |
| } |
| |
| extern "C" void |
| InputMethod_ConnectionWatchProc (Display *pDisplay, XPointer pClientData, |
| int nFileDescriptor, Bool bOpening, XPointer*) |
| { |
| SalXLib *pConnectionHandler = (SalXLib*)pClientData; |
| |
| if (pConnectionHandler == NULL) |
| return; |
| |
| if (bOpening) |
| { |
| pConnectionHandler->Insert (nFileDescriptor, pDisplay, |
| InputMethod_HasPendingEvent, |
| InputMethod_IsEventQueued, |
| InputMethod_HandleNextEvent); |
| } |
| else |
| { |
| pConnectionHandler->Remove (nFileDescriptor); |
| } |
| } |
| |
| Bool |
| SalI18N_InputMethod::AddConnectionWatch(Display *pDisplay, void *pConnectionHandler) |
| { |
| // sanity check |
| if (pDisplay == NULL || pConnectionHandler == NULL) |
| return False; |
| |
| // if we are not ml all the extended text input comes on the stock X queue, |
| // so there is no need to monitor additional file descriptors. |
| #ifndef SOLARIS |
| if (!mbMultiLingual || !mbUseable) |
| return False; |
| #endif |
| |
| // pConnectionHandler must be really a pointer to a SalXLib |
| Status nStatus = XAddConnectionWatch (pDisplay, InputMethod_ConnectionWatchProc, |
| (XPointer)pConnectionHandler); |
| return (Bool)nStatus; |
| } |
| |
| |
| |