blob: 3397cf190bb41e29c9cc1af39f458fa73b49ca3c [file] [log] [blame]
/**************************************************************
*
* 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;
}