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