xref: /trunk/main/vcl/unx/generic/app/i18n_im.cxx (revision c82f2877)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_vcl.hxx"
26 
27 #include <stdio.h>
28 #include <string.h>
29 
30 #ifdef LINUX
31 #  ifndef __USE_XOPEN
32 #    define __USE_XOPEN
33 #  endif
34 #endif
35 #include <poll.h>
36 
37 #include <tools/prex.h>
38 #include <X11/Xlocale.h>
39 #include <X11/Xlib.h>
40 #include <unx/XIM.h>
41 #include <tools/postx.h>
42 
43 #include "unx/salunx.h"
44 #include "unx/saldisp.hxx"
45 #include "unx/i18n_im.hxx"
46 #include "unx/i18n_status.hxx"
47 
48 #include <osl/thread.h>
49 #include <osl/process.h>
50 
51 using namespace vcl;
52 #include "unx/i18n_cb.hxx"
53 #if defined(SOLARIS) ||  defined(LINUX)
54 extern "C" char * XSetIMValues(XIM im, ...);
55 #endif
56 
57 // ------------------------------------------------------------------------------------
58 //
59 // kinput2 IME needs special key handling since key release events are filtered in
60 // preeditmode and XmbResetIC does not work
61 //
62 // ------------------------------------------------------------------------------------
63 
64 Bool
IMServerKinput2()65 IMServerKinput2 ()
66 {
67     const static char* p_xmodifiers = getenv ("XMODIFIERS");
68     const static Bool  b_kinput2    =    (p_xmodifiers != NULL)
69                                       && (strcmp(p_xmodifiers, "@im=kinput2") == 0);
70 
71     return b_kinput2;
72 }
73 
74 class XKeyEventOp : XKeyEvent
75 {
76     private:
77         void            init();
78 
79     public:
80                         XKeyEventOp();
81                         ~XKeyEventOp();
82 
83         XKeyEventOp&    operator= (const XKeyEvent &rEvent);
84         void            erase ();
85         Bool            match (const XKeyEvent &rEvent) const;
86 };
87 
88 void
init()89 XKeyEventOp::init()
90 {
91     type        = 0; /* serial = 0; */
92     send_event  = 0; display   = 0;
93     window      = 0; root      = 0;
94     subwindow   = 0; /* time   = 0; */
95  /* x           = 0; y         = 0; */
96  /* x_root      = 0; y_root    = 0; */
97     state       = 0; keycode   = 0;
98     same_screen = 0;
99 }
100 
XKeyEventOp()101 XKeyEventOp::XKeyEventOp()
102 {
103     init();
104 }
105 
~XKeyEventOp()106 XKeyEventOp::~XKeyEventOp()
107 {
108 }
109 
110 XKeyEventOp&
operator =(const XKeyEvent & rEvent)111 XKeyEventOp::operator= (const XKeyEvent &rEvent)
112 {
113     type        = rEvent.type;     /* serial  = rEvent.serial; */
114     send_event  = rEvent.send_event;  display = rEvent.display;
115     window      = rEvent.window;      root    = rEvent.root;
116     subwindow   = rEvent.subwindow;/* time    = rEvent.time;   */
117  /* x           = rEvent.x,           y       = rEvent.y;      */
118  /* x_root      = rEvent.x_root,      y_root  = rEvent.y_root; */
119     state       = rEvent.state;       keycode = rEvent.keycode;
120     same_screen = rEvent.same_screen;
121 
122     return *this;
123 }
124 
125 void
erase()126 XKeyEventOp::erase ()
127 {
128     init();
129 }
130 
131 Bool
match(const XKeyEvent & rEvent) const132 XKeyEventOp::match (const XKeyEvent &rEvent) const
133 {
134     return (   (type == XLIB_KeyPress   && rEvent.type == KeyRelease)
135             || (type == KeyRelease && rEvent.type == XLIB_KeyPress  ))
136          /* && serial      == rEvent.serial */
137             && send_event  == rEvent.send_event
138             && display     == rEvent.display
139             && window      == rEvent.window
140             && root        == rEvent.root
141             && subwindow   == rEvent.subwindow
142          /* && time        == rEvent.time
143             && x           == rEvent.x
144             && y           == rEvent.y
145             && x_root      == rEvent.x_root
146             && y_root      == rEvent.y_root */
147             && state       == rEvent.state
148             && keycode     == rEvent.keycode
149             && same_screen == rEvent.same_screen;
150 }
151 
152 // -------------------------------------------------------------------------
153 //
154 // locale handling
155 //
156 // -------------------------------------------------------------------------
157 
158 //  Locale handling of the operating system layer
159 
160 static char*
SetSystemLocale(const char * p_inlocale)161 SetSystemLocale( const char* p_inlocale )
162 {
163 	char *p_outlocale;
164 
165 	if ( (p_outlocale = setlocale(LC_ALL, p_inlocale)) == NULL )
166 	{
167 		fprintf( stderr, "I18N: Operating system doesn't support locale \"%s\"\n",
168 			p_inlocale );
169 	}
170 
171 	return p_outlocale;
172 }
173 
174 #ifdef SOLARIS
175 static void
SetSystemEnvironment(const rtl::OUString & rLocale)176 SetSystemEnvironment( const rtl::OUString& rLocale )
177 {
178     rtl::OUString LC_ALL_Var(RTL_CONSTASCII_USTRINGPARAM("LC_ALL"));
179     osl_setEnvironment(LC_ALL_Var.pData, rLocale.pData);
180 
181     rtl::OUString LANG_Var(RTL_CONSTASCII_USTRINGPARAM("LANG"));
182     osl_setEnvironment(LANG_Var.pData, rLocale.pData);
183 }
184 #endif
185 
186 static Bool
IsPosixLocale(const char * p_locale)187 IsPosixLocale( const char* p_locale )
188 {
189 	if ( p_locale == NULL )
190 		return False;
191 	if ( (p_locale[ 0 ] == 'C') && (p_locale[ 1 ] == '\0') )
192 		return True;
193 	if ( strncmp(p_locale, "POSIX", sizeof("POSIX")) == 0 )
194 		return True;
195 
196 	return False;
197 }
198 
199 //  Locale handling of the X Window System layer
200 
201 static Bool
IsXWindowCompatibleLocale(const char * p_locale)202 IsXWindowCompatibleLocale( const char* p_locale )
203 {
204 	if ( p_locale == NULL )
205 		return False;
206 
207 	if ( !XSupportsLocale() )
208 	{
209 		fprintf (stderr, "I18N: X Window System doesn't support locale \"%s\"\n",
210 				p_locale );
211 		return False;
212 	}
213 	return True;
214 }
215 
216 // Set the operating system locale prior to trying to open an
217 // XIM InputMethod.
218 // Handle the cases where the current locale is either not supported by the
219 // operating system (LANG=gaga) or by the XWindow system (LANG=aa_ER@saaho)
220 // by providing a fallback.
221 // Upgrade "C" or "POSIX" to "en_US" locale to allow umlauts and accents
222 // see i8988, i9188, i8930, i16318
223 // on Solaris the environment needs to be set equivalent to the locale (#i37047#)
224 
225 Bool
SetLocale(const char * pLocale)226 SalI18N_InputMethod::SetLocale( const char* pLocale )
227 {
228 	// check whether we want an Input Method engine, if we don't we
229 	// do not need to set the locale
230 	if ( mbUseable )
231 	{
232 		char *locale = SetSystemLocale( pLocale );
233 		if ( (!IsXWindowCompatibleLocale(locale)) || IsPosixLocale(locale) )
234 		{
235             osl_setThreadTextEncoding (RTL_TEXTENCODING_ISO_8859_1);
236             locale = SetSystemLocale( "en_US" );
237             #ifdef SOLARIS
238             SetSystemEnvironment( rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("en_US")) );
239             #endif
240 		    if (! IsXWindowCompatibleLocale(locale))
241             {
242 			    locale = SetSystemLocale( "C" );
243                 #ifdef SOLARIS
244                 SetSystemEnvironment( rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("C")) );
245                 #endif
246 		        if (! IsXWindowCompatibleLocale(locale))
247 				    mbUseable = False;
248             }
249 		}
250 
251         // must not fail if mbUseable since XSupportsLocale() asserts success
252 		if ( mbUseable && XSetLocaleModifiers("") == NULL )
253 		{
254 			fprintf (stderr, "I18N: Can't set X modifiers for locale \"%s\"\n",
255 				locale);
256 			mbUseable = False;
257 		}
258 	}
259 
260 	return mbUseable;
261 }
262 
263 Bool
PosixLocale()264 SalI18N_InputMethod::PosixLocale()
265 {
266     if (mbMultiLingual)
267         return False;
268     if (maMethod)
269         return IsPosixLocale (XLocaleOfIM (maMethod));
270     return False;
271 }
272 
273 // ------------------------------------------------------------------------
274 //
275 // Constructor / Destructor / Initialisation
276 //
277 // ------------------------------------------------------------------------
278 
SalI18N_InputMethod()279 SalI18N_InputMethod::SalI18N_InputMethod( ) : mbUseable( bUseInputMethodDefault ),
280 											  mbMultiLingual( False ),
281                                               maMethod( (XIM)NULL ),
282 								  			  mpStyles( (XIMStyles*)NULL )
283 {
284 	const char *pUseInputMethod = getenv( "SAL_USEINPUTMETHOD" );
285 	if ( pUseInputMethod != NULL )
286 		mbUseable = pUseInputMethod[0] != '\0' ;
287 }
288 
~SalI18N_InputMethod()289 SalI18N_InputMethod::~SalI18N_InputMethod()
290 {
291     ::vcl::I18NStatus::free();
292 	if ( mpStyles != NULL )
293 		XFree( mpStyles );
294 	if ( maMethod != NULL )
295 		XCloseIM ( maMethod );
296 }
297 
298 //
299 // XXX
300 // debug routine: lets have a look at the provided method styles
301 //
302 
303 #if OSL_DEBUG_LEVEL > 1
304 
305 extern "C" char*
GetMethodName(XIMStyle nStyle,char * pBuf,int nBufSize)306 GetMethodName( XIMStyle nStyle, char *pBuf, int nBufSize)
307 {
308 	struct StyleName {
309 		const XIMStyle nStyle;
310 		const char    *pName;
311 		const int      nNameLen;
312 	};
313 
314 	StyleName *pDescPtr;
315 	static const StyleName pDescription[] = {
316 		{ XIMPreeditArea, 	   "PreeditArea ", 	   sizeof("PreeditArea ")	},
317 		{ XIMPreeditCallbacks, "PreeditCallbacks ",sizeof("PreeditCallbacks ")},
318 		{ XIMPreeditPosition,  "PreeditPosition ", sizeof("PreeditPosition ") },
319 		{ XIMPreeditNothing,   "PreeditNothing ",  sizeof("PreeditNothing ")  },
320 		{ XIMPreeditNone, 	   "PreeditNone ",	   sizeof("PreeditNone ")	},
321 		{ XIMStatusArea, 	   "StatusArea ",      sizeof("StatusArea ")	},
322 		{ XIMStatusCallbacks,  "StatusCallbacks ", sizeof("StatusCallbacks ") },
323 		{ XIMStatusNothing,    "StatusNothing ",   sizeof("StatusNothing ")	},
324 		{ XIMStatusNone, 	   "StatusNone ",      sizeof("StatusNone ")	},
325 		{ 0, "NULL", 0 }
326 	};
327 
328 	if ( nBufSize > 0 )
329 		pBuf[0] = '\0';
330 
331 	char *pBufPtr = pBuf;
332 	for ( pDescPtr = const_cast<StyleName*>(pDescription); pDescPtr->nStyle != 0; pDescPtr++ )
333 	{
334 		int nSize = pDescPtr->nNameLen - 1;
335 		if ( (nStyle & pDescPtr->nStyle) && (nBufSize > nSize) )
336 		{
337 			strncpy( pBufPtr, pDescPtr->pName, nSize + 1);
338 			pBufPtr  += nSize;
339 			nBufSize -= nSize;
340 		}
341 	}
342 
343 	return pBuf;
344 }
345 
346 extern "C" void
PrintInputStyle(XIMStyles * pStyle)347 PrintInputStyle( XIMStyles *pStyle )
348 {
349 	char pBuf[ 128 ];
350 	int  nBuf = sizeof( pBuf );
351 
352 	if ( pStyle == NULL )
353 		fprintf( stderr, "no input method styles\n");
354 	else
355 	for ( int nStyle = 0; nStyle < pStyle->count_styles; nStyle++ )
356 	{
357 		fprintf( stderr, "style #%i = %s\n", nStyle,
358 		  	GetMethodName(pStyle->supported_styles[nStyle], pBuf, nBuf) );
359 	}
360 }
361 
362 #endif
363 
364 //
365 // this is the real constructing routine, since locale setting has to be done
366 // prior to xopendisplay, the xopenim call has to be delayed
367 //
368 
369 Bool
CreateMethod(Display * pDisplay)370 SalI18N_InputMethod::CreateMethod ( Display *pDisplay )
371 {
372 	if ( mbUseable )
373 	{
374         const bool bTryMultiLingual =
375         #ifdef LINUX
376                         false;
377         #else
378                         true;
379         #endif
380 		if ( bTryMultiLingual && getenv("USE_XOPENIM") == NULL )
381 		{
382 			mbMultiLingual = True; // set ml-input flag to create input-method
383 	    	maMethod = XvaOpenIM(pDisplay, NULL, NULL, NULL,
384 					XNMultiLingualInput, mbMultiLingual, /* dummy */
385 			 		(void *)0);
386 			// get ml-input flag from input-method
387 			if ( maMethod == (XIM)NULL )
388 				mbMultiLingual = False;
389 			else
390 			if ( XGetIMValues(maMethod,
391 					XNMultiLingualInput, &mbMultiLingual, NULL ) != NULL )
392 				mbMultiLingual = False;
393             if( mbMultiLingual )
394             {
395                 XIMUnicodeCharacterSubsets* subsets;
396                 if( XGetIMValues( maMethod,
397                                   XNQueryUnicodeCharacterSubset, &subsets, NULL ) == NULL )
398                 {
399 #if OSL_DEBUG_LEVEL > 1
400                     fprintf( stderr, "IM reports %d subsets: ", subsets->count_subsets );
401 #endif
402                     I18NStatus& rStatus( I18NStatus::get() );
403                     rStatus.clearChoices();
404                     for( int i = 0; i < subsets->count_subsets; i++ )
405                     {
406 #if OSL_DEBUG_LEVEL > 1
407                         fprintf( stderr,"\"%s\" ", subsets->supported_subsets[i].name );
408 #endif
409                         rStatus.addChoice( String( subsets->supported_subsets[i].name, RTL_TEXTENCODING_UTF8 ), &subsets->supported_subsets[i] );
410                     }
411 #if OSL_DEBUG_LEVEL > 1
412                     fprintf( stderr, "\n" );
413 #endif
414                 }
415 #if OSL_DEBUG_LEVEL > 1
416                 else
417                     fprintf( stderr, "query subsets failed\n" );
418 #endif
419             }
420 		}
421 		else
422 	    {
423 		    maMethod = XOpenIM(pDisplay, NULL, NULL, NULL);
424 			mbMultiLingual = False;
425         }
426 
427         if ((maMethod == (XIM)NULL) && (getenv("XMODIFIERS") != NULL))
428         {
429 				rtl::OUString envVar(RTL_CONSTASCII_USTRINGPARAM("XMODIFIERS"));
430 				osl_clearEnvironment(envVar.pData);
431                 XSetLocaleModifiers("");
432                 maMethod = XOpenIM(pDisplay, NULL, NULL, NULL);
433 			    mbMultiLingual = False;
434         }
435 
436 		if ( maMethod != (XIM)NULL )
437 		{
438 			if (   XGetIMValues(maMethod, XNQueryInputStyle, &mpStyles, NULL)
439 				!= NULL)
440 				mbUseable = False;
441             #if OSL_DEBUG_LEVEL > 1
442 			fprintf(stderr, "Creating %s-Lingual InputMethod\n",
443 				mbMultiLingual ? "Multi" : "Mono" );
444 			PrintInputStyle( mpStyles );
445 			#endif
446 		}
447 		else
448 		{
449 			mbUseable = False;
450 		}
451 	}
452 
453     #if OSL_DEBUG_LEVEL > 1
454 	if ( !mbUseable )
455 		fprintf(stderr, "input method creation failed\n");
456 	#endif
457 
458     maDestroyCallback.callback	  = (XIMProc)IM_IMDestroyCallback;
459 	maDestroyCallback.client_data = (XPointer)this;
460     if (mbUseable && maMethod != NULL)
461         XSetIMValues(maMethod, XNDestroyCallback, &maDestroyCallback, NULL);
462 
463 	return mbUseable;
464 }
465 
466 //
467 // give IM the opportunity to look at the event, and possibly hide it
468 //
469 
470 Bool
FilterEvent(XEvent * pEvent,XLIB_Window window)471 SalI18N_InputMethod::FilterEvent( XEvent *pEvent, XLIB_Window window	)
472 {
473 	if (!mbUseable)
474         return False;
475 
476     Bool bFilterEvent = XFilterEvent (pEvent, window);
477 
478     if (pEvent->type != XLIB_KeyPress && pEvent->type != KeyRelease)
479         return bFilterEvent;
480 
481     /*
482      * fix broken key release handling of some IMs
483      */
484     XKeyEvent*         pKeyEvent = &(pEvent->xkey);
485     static XKeyEventOp maLastKeyPress;
486 
487     if (bFilterEvent)
488     {
489         if (pKeyEvent->type == KeyRelease)
490             bFilterEvent = !maLastKeyPress.match (*pKeyEvent);
491         maLastKeyPress.erase();
492     }
493     else /* (!bFilterEvent) */
494     {
495         if (pKeyEvent->type == XLIB_KeyPress)
496             maLastKeyPress = *pKeyEvent;
497         else
498             maLastKeyPress.erase();
499     }
500 
501     return bFilterEvent;
502 }
503 
504 void
HandleDestroyIM()505 SalI18N_InputMethod::HandleDestroyIM()
506 {
507     mbUseable       = False;
508     mbMultiLingual  = False;
509     maMethod        = NULL;
510 }
511 
512 // ------------------------------------------------------------------------
513 //
514 // add a connection watch into the SalXLib yieldTable to allow iiimp
515 // connection processing: soffice waits in select() not in XNextEvent(), so
516 // there may be requests pending on the iiimp internal connection that will
517 // not be processed until XNextEvent is called the next time. If we do not
518 // have the focus because the atok12 lookup choice aux window has it we stay
519 // deaf and dump otherwise.
520 //
521 // ------------------------------------------------------------------------
522 
523 int
InputMethod_HasPendingEvent(int nFileDescriptor,void * pData)524 InputMethod_HasPendingEvent(int nFileDescriptor, void *pData)
525 {
526 	if (pData == NULL)
527 		return 0;
528 
529 	struct pollfd aFileDescriptor;
530 	#ifdef SOLARIS
531 	nfds_t 		  nNumDescriptor = 1;
532 	#else
533 	unsigned int	  nNumDescriptor = 1;
534 	#endif
535 	aFileDescriptor.fd      = nFileDescriptor;
536 	aFileDescriptor.events  = POLLRDNORM;
537 	aFileDescriptor.revents = 0;
538 
539 	int nPoll = poll (&aFileDescriptor, nNumDescriptor, 0 /* timeout */ );
540 
541 	if (nPoll > 0)
542 	{
543 		/* at least some conditions in revent are set */
544 		if (   (aFileDescriptor.revents & POLLHUP)
545 			|| (aFileDescriptor.revents & POLLERR)
546 			|| (aFileDescriptor.revents & POLLNVAL))
547 			return 0; /* oops error condition set */
548 
549 		if (aFileDescriptor.revents & POLLRDNORM)
550 			return 1; /* success */
551 	}
552 
553 	/* nPoll == 0 means timeout, nPoll < 0 means error */
554 	return 0;
555 }
556 
557 int
InputMethod_IsEventQueued(int nFileDescriptor,void * pData)558 InputMethod_IsEventQueued(int nFileDescriptor, void *pData)
559 {
560 	return InputMethod_HasPendingEvent (nFileDescriptor, pData);
561 }
562 
563 int
InputMethod_HandleNextEvent(int nFileDescriptor,void * pData)564 InputMethod_HandleNextEvent(int nFileDescriptor, void *pData)
565 {
566 	if (pData != NULL)
567 		XProcessInternalConnection((Display*)pData, nFileDescriptor);
568 
569 	return 0;
570 }
571 
572 extern "C" void
InputMethod_ConnectionWatchProc(Display * pDisplay,XPointer pClientData,int nFileDescriptor,Bool bOpening,XPointer *)573 InputMethod_ConnectionWatchProc (Display *pDisplay, XPointer pClientData,
574 	int nFileDescriptor, Bool bOpening, XPointer*)
575 {
576 	SalXLib *pConnectionHandler = (SalXLib*)pClientData;
577 
578 	if (pConnectionHandler == NULL)
579 		return;
580 
581 	if (bOpening)
582 	{
583 		pConnectionHandler->Insert (nFileDescriptor, pDisplay,
584 									InputMethod_HasPendingEvent,
585 									InputMethod_IsEventQueued,
586 									InputMethod_HandleNextEvent);
587 	}
588 	else
589 	{
590 		pConnectionHandler->Remove (nFileDescriptor);
591 	}
592 }
593 
594 Bool
AddConnectionWatch(Display * pDisplay,void * pConnectionHandler)595 SalI18N_InputMethod::AddConnectionWatch(Display *pDisplay, void *pConnectionHandler)
596 {
597 	// sanity check
598 	if (pDisplay == NULL || pConnectionHandler == NULL)
599 		return False;
600 
601 	// if we are not ml all the extended text input comes on the stock X queue,
602 	// so there is no need to monitor additional file descriptors.
603 #ifndef SOLARIS
604  	if (!mbMultiLingual || !mbUseable)
605  		return False;
606 #endif
607 
608 	// pConnectionHandler must be really a pointer to a SalXLib
609 	Status nStatus = XAddConnectionWatch (pDisplay, InputMethod_ConnectionWatchProc,
610 										  (XPointer)pConnectionHandler);
611 	return (Bool)nStatus;
612 }
613 
614 
615 
616