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