diff options
Diffstat (limited to 'vcl/unx/source/app/i18n_im.cxx')
-rw-r--r-- | vcl/unx/source/app/i18n_im.cxx | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/vcl/unx/source/app/i18n_im.cxx b/vcl/unx/source/app/i18n_im.cxx new file mode 100644 index 000000000000..c797da34e76c --- /dev/null +++ b/vcl/unx/source/app/i18n_im.cxx @@ -0,0 +1,618 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 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 <XIM.h> +#include <tools/postx.h> + +#include <salunx.h> +#include <saldisp.hxx> +#include "i18n_im.hxx" +#include <i18n_status.hxx> + +#include <osl/thread.h> +#include <osl/process.h> + +using namespace vcl; +#include "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; +} + + + |