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