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 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 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 101 XKeyEventOp::XKeyEventOp() 102 { 103 init(); 104 } 105 106 XKeyEventOp::~XKeyEventOp() 107 { 108 } 109 110 XKeyEventOp& 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 126 XKeyEventOp::erase () 127 { 128 init(); 129 } 130 131 Bool 132 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* 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 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 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 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 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 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 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 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* 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 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 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 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 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 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 558 InputMethod_IsEventQueued(int nFileDescriptor, void *pData) 559 { 560 return InputMethod_HasPendingEvent (nFileDescriptor, pData); 561 } 562 563 int 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 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 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