1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20 #include <X11/Xlib.h>
21
22 #include <unx/i18n_ic.hxx>
23 #include <unx/i18n_im.hxx>
24
25 #include <unx/salframe.h>
26 #include <unx/saldisp.hxx>
27
28 using namespace vcl;
29
sendEmptyCommit(SalFrame * pFrame)30 static void sendEmptyCommit( SalFrame* pFrame )
31 {
32 vcl::DeletionListener aDel( pFrame );
33
34 SalExtTextInputEvent aEmptyEv;
35 aEmptyEv.mpTextAttr = nullptr;
36 aEmptyEv.maText.clear();
37 aEmptyEv.mnCursorPos = 0;
38 aEmptyEv.mnCursorFlags = 0;
39 pFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void*>(&aEmptyEv) );
40 if( ! aDel.isDeleted() )
41 pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
42 }
43
44 // Constructor / Destructor, the InputContext is bound to the SalFrame, as it
45 // needs the shell window as a focus window
46
~SalI18N_InputContext()47 SalI18N_InputContext::~SalI18N_InputContext()
48 {
49 if ( maContext != nullptr )
50 XDestroyIC( maContext );
51 if ( mpAttributes != nullptr )
52 XFree( mpAttributes );
53 if ( mpStatusAttributes != nullptr )
54 XFree( mpStatusAttributes );
55 if ( mpPreeditAttributes != nullptr )
56 XFree( mpPreeditAttributes );
57
58 if (maClientData.aText.pUnicodeBuffer != nullptr)
59 free(maClientData.aText.pUnicodeBuffer);
60 if (maClientData.aText.pCharStyle != nullptr)
61 free(maClientData.aText.pCharStyle);
62 }
63
64 // convenience routine to add items to a XVaNestedList
65
66 static XVaNestedList
XVaAddToNestedList(XVaNestedList a_srclist,char * name,XPointer value)67 XVaAddToNestedList( XVaNestedList a_srclist, char* name, XPointer value )
68 {
69 XVaNestedList a_dstlist;
70
71 // if ( value == NULL )
72 // return a_srclist;
73
74 if ( a_srclist == nullptr )
75 {
76 a_dstlist = XVaCreateNestedList(
77 0,
78 name, value,
79 nullptr );
80 }
81 else
82 {
83 a_dstlist = XVaCreateNestedList(
84 0,
85 XNVaNestedList, a_srclist,
86 name, value,
87 nullptr );
88 }
89
90 return a_dstlist != nullptr ? a_dstlist : a_srclist ;
91 }
92
93 // convenience routine to create a fontset
94
95 static XFontSet
get_font_set(Display * p_display)96 get_font_set( Display *p_display )
97 {
98 static XFontSet p_font_set = nullptr;
99
100 if (p_font_set == nullptr)
101 {
102 char **pp_missing_list;
103 int n_missing_count;
104 char *p_default_string;
105
106 p_font_set = XCreateFontSet(p_display, "-*",
107 &pp_missing_list, &n_missing_count, &p_default_string);
108 }
109
110 return p_font_set;
111 }
112
113 static const XIMStyle g_nSupportedStatusStyle(
114 XIMStatusCallbacks |
115 XIMStatusNothing |
116 XIMStatusNone
117 );
118
119 // Constructor for an InputContext (IC)
120
SalI18N_InputContext(SalFrame * pFrame)121 SalI18N_InputContext::SalI18N_InputContext ( SalFrame *pFrame ) :
122 mbUseable( True ),
123 maContext( nullptr ),
124 mnSupportedPreeditStyle(
125 XIMPreeditCallbacks |
126 XIMPreeditNothing |
127 XIMPreeditNone
128 ),
129 mnStatusStyle( 0 ),
130 mnPreeditStyle( 0 ),
131 mpAttributes( nullptr ),
132 mpStatusAttributes( nullptr ),
133 mpPreeditAttributes( nullptr )
134 {
135 #ifdef __sun
136 static const char* pIIIMPEnable = getenv( "SAL_DISABLE_OWN_IM_STATUS" );
137 if( pIIIMPEnable && *pIIIMPEnable )
138 mnSupportedStatusStyle &= ~XIMStatusCallbacks;
139 #endif
140
141 memset(&maPreeditStartCallback, 0, sizeof(maPreeditStartCallback));
142 memset(&maPreeditDoneCallback, 0, sizeof(maPreeditDoneCallback));
143 memset(&maPreeditDrawCallback, 0, sizeof(maPreeditDrawCallback));
144 memset(&maPreeditCaretCallback, 0, sizeof(maPreeditCaretCallback));
145 memset(&maCommitStringCallback, 0, sizeof(maCommitStringCallback));
146 memset(&maSwitchIMCallback, 0, sizeof(maSwitchIMCallback));
147 memset(&maDestroyCallback, 0, sizeof(maDestroyCallback));
148
149 maClientData.aText.pUnicodeBuffer = nullptr;
150 maClientData.aText.pCharStyle = nullptr;
151 maClientData.aInputEv.mpTextAttr = nullptr;
152 maClientData.aInputEv.mnCursorPos = 0;
153 maClientData.aInputEv.mnCursorFlags = 0;
154
155 SalI18N_InputMethod *pInputMethod;
156 pInputMethod = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetInputMethod();
157
158 mnSupportedPreeditStyle = XIMPreeditCallbacks | XIMPreeditPosition
159 | XIMPreeditNothing | XIMPreeditNone;
160 if (pInputMethod->UseMethod()
161 && SupportInputMethodStyle( pInputMethod->GetSupportedStyles() ) )
162 {
163 const SystemEnvData* pEnv = pFrame->GetSystemData();
164 ::Window aClientWindow = pEnv->aShellWindow;
165 ::Window aFocusWindow = pEnv->aWindow;
166
167 // for status callbacks and commit string callbacks
168 #define PREEDIT_BUFSZ 16
169 maClientData.eState = PreeditStatus::StartPending;
170 maClientData.pFrame = pFrame;
171 maClientData.aText.pUnicodeBuffer =
172 static_cast<sal_Unicode*>(malloc(PREEDIT_BUFSZ * sizeof(sal_Unicode)));
173 maClientData.aText.pCharStyle =
174 static_cast<XIMFeedback*>(malloc(PREEDIT_BUFSZ * sizeof(XIMFeedback)));
175 maClientData.aText.nSize = PREEDIT_BUFSZ;
176 maClientData.aText.nLength = 0;
177
178 // Status attributes
179
180 switch ( mnStatusStyle )
181 {
182 case XIMStatusCallbacks:
183 {
184 static XIMCallback aStatusStartCallback;
185 static XIMCallback aStatusDoneCallback;
186 static XIMCallback aStatusDrawCallback;
187
188 aStatusStartCallback.callback = reinterpret_cast<XIMProc>(StatusStartCallback);
189 aStatusStartCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
190 aStatusDoneCallback.callback = reinterpret_cast<XIMProc>(StatusDoneCallback);
191 aStatusDoneCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
192 aStatusDrawCallback.callback = reinterpret_cast<XIMProc>(StatusDrawCallback);
193 aStatusDrawCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
194
195 mpStatusAttributes = XVaCreateNestedList (
196 0,
197 XNStatusStartCallback, &aStatusStartCallback,
198 XNStatusDoneCallback, &aStatusDoneCallback,
199 XNStatusDrawCallback, &aStatusDrawCallback,
200 nullptr );
201
202 break;
203 }
204
205 case XIMStatusArea:
206 /* not supported */
207 break;
208
209 case XIMStatusNone:
210 case XIMStatusNothing:
211 default:
212 /* no arguments needed */
213 break;
214 }
215
216 // set preedit attributes
217
218 switch ( mnPreeditStyle )
219 {
220 case XIMPreeditCallbacks:
221
222 maPreeditCaretCallback.callback = reinterpret_cast<XIMProc>(PreeditCaretCallback);
223 maPreeditStartCallback.callback = reinterpret_cast<XIMProc>(PreeditStartCallback);
224 maPreeditDoneCallback.callback = reinterpret_cast<XIMProc>(PreeditDoneCallback);
225 maPreeditDrawCallback.callback = reinterpret_cast<XIMProc>(PreeditDrawCallback);
226 maPreeditCaretCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
227 maPreeditStartCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
228 maPreeditDoneCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
229 maPreeditDrawCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
230
231 mpPreeditAttributes = XVaCreateNestedList (
232 0,
233 XNPreeditStartCallback, &maPreeditStartCallback,
234 XNPreeditDoneCallback, &maPreeditDoneCallback,
235 XNPreeditDrawCallback, &maPreeditDrawCallback,
236 XNPreeditCaretCallback, &maPreeditCaretCallback,
237 nullptr );
238
239 break;
240
241 case XIMPreeditArea:
242 /* not supported */
243 break;
244
245 case XIMPreeditPosition:
246 {
247 // spot location
248 SalExtTextInputPosEvent aPosEvent;
249 pFrame->CallCallback(SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent));
250
251 static XPoint aSpot;
252 aSpot.x = aPosEvent.mnX + aPosEvent.mnWidth;
253 aSpot.y = aPosEvent.mnY + aPosEvent.mnHeight;
254
255 // create attributes for preedit position style
256 mpPreeditAttributes = XVaCreateNestedList (
257 0,
258 XNSpotLocation, &aSpot,
259 nullptr );
260
261 // XCreateIC() fails on Redflag Linux 2.0 if there is no
262 // fontset though the data itself is not evaluated nor is
263 // it required according to the X specs.
264 Display* pDisplay = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay();
265 XFontSet pFontSet = get_font_set(pDisplay);
266
267 if (pFontSet != nullptr)
268 {
269 mpPreeditAttributes = XVaAddToNestedList( mpPreeditAttributes,
270 const_cast<char*>(XNFontSet), reinterpret_cast<XPointer>(pFontSet));
271 }
272
273 break;
274 }
275
276 case XIMPreeditNone:
277 case XIMPreeditNothing:
278 default:
279 /* no arguments needed */
280 break;
281 }
282
283 // Create the InputContext by giving it exactly the information it
284 // deserves, because inappropriate attributes
285 // let XCreateIC fail on Solaris (eg. for C locale)
286
287 mpAttributes = XVaCreateNestedList(
288 0,
289 XNFocusWindow, aFocusWindow,
290 XNClientWindow, aClientWindow,
291 XNInputStyle, mnPreeditStyle | mnStatusStyle,
292 nullptr );
293
294 if ( mnPreeditStyle != XIMPreeditNone )
295 {
296 #if defined LINUX || defined FREEBSD || defined NETBSD || defined OPENBSD || defined DRAGONFLY
297 if ( mpPreeditAttributes != nullptr )
298 #endif
299 mpAttributes = XVaAddToNestedList( mpAttributes,
300 const_cast<char*>(XNPreeditAttributes), static_cast<XPointer>(mpPreeditAttributes) );
301 }
302 if ( mnStatusStyle != XIMStatusNone )
303 {
304 #if defined LINUX || defined FREEBSD || defined NETBSD || defined OPENBSD || defined DRAGONFLY
305 if ( mpStatusAttributes != nullptr )
306 #endif
307 mpAttributes = XVaAddToNestedList( mpAttributes,
308 const_cast<char*>(XNStatusAttributes), static_cast<XPointer>(mpStatusAttributes) );
309 }
310 maContext = XCreateIC( pInputMethod->GetMethod(),
311 XNVaNestedList, mpAttributes,
312 nullptr );
313 }
314
315 if ( maContext == nullptr )
316 {
317 #if OSL_DEBUG_LEVEL > 1
318 fprintf(stderr, "input context creation failed\n");
319 #endif
320
321 mbUseable = False;
322
323 if ( mpAttributes != nullptr )
324 XFree( mpAttributes );
325 if ( mpStatusAttributes != nullptr )
326 XFree( mpStatusAttributes );
327 if ( mpPreeditAttributes != nullptr )
328 XFree( mpPreeditAttributes );
329 if ( maClientData.aText.pUnicodeBuffer != nullptr )
330 free ( maClientData.aText.pUnicodeBuffer );
331 if ( maClientData.aText.pCharStyle != nullptr )
332 free ( maClientData.aText.pCharStyle );
333
334 mpAttributes = nullptr;
335 mpStatusAttributes = nullptr;
336 mpPreeditAttributes = nullptr;
337 maClientData.aText.pUnicodeBuffer = nullptr;
338 maClientData.aText.pCharStyle = nullptr;
339 }
340
341 if ( maContext != nullptr)
342 {
343 maDestroyCallback.callback = IC_IMDestroyCallback;
344 maDestroyCallback.client_data = reinterpret_cast<XPointer>(this);
345 XSetICValues( maContext,
346 XNDestroyCallback, &maDestroyCallback,
347 nullptr );
348 }
349 }
350
351 // In Solaris 8 the status window does not unmap if the frame unmapps, so
352 // unmap it the hard way
353
354 void
Unmap()355 SalI18N_InputContext::Unmap()
356 {
357 UnsetICFocus();
358 maClientData.pFrame = nullptr;
359 }
360
361 void
Map(SalFrame * pFrame)362 SalI18N_InputContext::Map( SalFrame *pFrame )
363 {
364 if( mbUseable )
365 {
366 if( pFrame )
367 {
368 if ( maContext == nullptr )
369 {
370 SalI18N_InputMethod *pInputMethod;
371 pInputMethod = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetInputMethod();
372
373 maContext = XCreateIC( pInputMethod->GetMethod(),
374 XNVaNestedList, mpAttributes,
375 nullptr );
376 }
377 if( maClientData.pFrame != pFrame )
378 SetICFocus( pFrame );
379 }
380 }
381 }
382
383 // Handle DestroyCallbacks
384 // in fact this is a callback called from the XNDestroyCallback
385
386 void
HandleDestroyIM()387 SalI18N_InputContext::HandleDestroyIM()
388 {
389 maContext = nullptr; // don't change
390 mbUseable = False;
391 }
392
393 // make sure, the input method gets all the X-Events it needs, this is only
394 // called once on each frame, it relies on a valid maContext
395
396 void
ExtendEventMask(::Window aFocusWindow)397 SalI18N_InputContext::ExtendEventMask( ::Window aFocusWindow )
398 {
399 unsigned long nIMEventMask;
400 XWindowAttributes aWindowAttributes;
401
402 if ( mbUseable )
403 {
404 Display *pDisplay = XDisplayOfIM( XIMOfIC(maContext) );
405
406 XGetWindowAttributes( pDisplay, aFocusWindow,
407 &aWindowAttributes );
408 XGetICValues ( maContext,
409 XNFilterEvents, &nIMEventMask,
410 nullptr);
411 nIMEventMask |= aWindowAttributes.your_event_mask;
412 XSelectInput ( pDisplay, aFocusWindow, nIMEventMask );
413 }
414 }
415
416 // tune the styles provided by the input method with the supported one
417
418 unsigned int
GetWeightingOfIMStyle(XIMStyle nStyle)419 SalI18N_InputContext::GetWeightingOfIMStyle( XIMStyle nStyle )
420 {
421 struct StyleWeightingT {
422 const XIMStyle nStyle;
423 const unsigned int nWeight;
424 };
425
426 StyleWeightingT const *pWeightPtr;
427 static const StyleWeightingT pWeight[] = {
428 { XIMPreeditCallbacks, 0x10000000 },
429 { XIMPreeditPosition, 0x02000000 },
430 { XIMPreeditArea, 0x01000000 },
431 { XIMPreeditNothing, 0x00100000 },
432 { XIMPreeditNone, 0x00010000 },
433 { XIMStatusCallbacks, 0x1000 },
434 { XIMStatusArea, 0x0100 },
435 { XIMStatusNothing, 0x0010 },
436 { XIMStatusNone, 0x0001 },
437 { 0, 0x0 }
438 };
439
440 int nWeight = 0;
441 for ( pWeightPtr = pWeight; pWeightPtr->nStyle != 0; pWeightPtr++ )
442 {
443 if ( (pWeightPtr->nStyle & nStyle) != 0 )
444 nWeight += pWeightPtr->nWeight;
445 }
446 return nWeight;
447 }
448
449 bool
IsSupportedIMStyle(XIMStyle nStyle) const450 SalI18N_InputContext::IsSupportedIMStyle( XIMStyle nStyle ) const
451 {
452 return (nStyle & mnSupportedPreeditStyle)
453 && (nStyle & g_nSupportedStatusStyle);
454 }
455
456 bool
SupportInputMethodStyle(XIMStyles const * pIMStyles)457 SalI18N_InputContext::SupportInputMethodStyle( XIMStyles const *pIMStyles )
458 {
459 mnPreeditStyle = 0;
460 mnStatusStyle = 0;
461
462 if ( pIMStyles != nullptr )
463 {
464 int nBestScore = 0;
465 int nActualScore = 0;
466
467 // check whether the XIM supports one of the desired styles
468 // only a single preedit and a single status style must occur
469 // in an input method style. Hideki said so, so i trust him
470 for ( int nStyle = 0; nStyle < pIMStyles->count_styles; nStyle++ )
471 {
472 XIMStyle nProvidedStyle = pIMStyles->supported_styles[ nStyle ];
473 if ( IsSupportedIMStyle(nProvidedStyle) )
474 {
475 nActualScore = GetWeightingOfIMStyle( nProvidedStyle );
476 if ( nActualScore >= nBestScore )
477 {
478 nBestScore = nActualScore;
479 mnPreeditStyle = nProvidedStyle & mnSupportedPreeditStyle;
480 mnStatusStyle = nProvidedStyle & g_nSupportedStatusStyle;
481 }
482 }
483 }
484 }
485
486 return (mnPreeditStyle != 0) && (mnStatusStyle != 0) ;
487 }
488
489 // handle extended and normal key input
490
491 void
CommitKeyEvent(sal_Unicode const * pText,std::size_t nLength)492 SalI18N_InputContext::CommitKeyEvent(sal_Unicode const * pText, std::size_t nLength)
493 {
494 if (nLength == 1 && IsControlCode(pText[0]))
495 return;
496
497 if( maClientData.pFrame )
498 {
499 SalExtTextInputEvent aTextEvent;
500
501 aTextEvent.mpTextAttr = nullptr;
502 aTextEvent.mnCursorPos = nLength;
503 aTextEvent.maText = OUString(pText, nLength);
504 aTextEvent.mnCursorFlags = 0;
505
506 maClientData.pFrame->CallCallback(SalEvent::ExtTextInput, static_cast<void*>(&aTextEvent));
507 maClientData.pFrame->CallCallback(SalEvent::EndExtTextInput, nullptr);
508 }
509 #if OSL_DEBUG_LEVEL > 1
510 else
511 fprintf(stderr, "CommitKeyEvent without frame\n" );
512 #endif
513 }
514
515 int
UpdateSpotLocation()516 SalI18N_InputContext::UpdateSpotLocation()
517 {
518 if (maContext == nullptr || maClientData.pFrame == nullptr)
519 return -1;
520
521 SalExtTextInputPosEvent aPosEvent;
522 maClientData.pFrame->CallCallback(SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent));
523
524 XPoint aSpot;
525 aSpot.x = aPosEvent.mnX + aPosEvent.mnWidth;
526 aSpot.y = aPosEvent.mnY + aPosEvent.mnHeight;
527
528 XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &aSpot, nullptr);
529 XSetICValues(maContext, XNPreeditAttributes, preedit_attr, nullptr);
530 XFree(preedit_attr);
531
532 return 0;
533 }
534
535 // set and unset the focus for the Input Context
536 // the context may be NULL despite it is usable if the framewindow is
537 // in unmapped state
538
539 void
SetICFocus(SalFrame * pFocusFrame)540 SalI18N_InputContext::SetICFocus( SalFrame* pFocusFrame )
541 {
542 if ( mbUseable && (maContext != nullptr) )
543 {
544 maClientData.pFrame = pFocusFrame;
545
546 const SystemEnvData* pEnv = pFocusFrame->GetSystemData();
547 ::Window aClientWindow = pEnv->aShellWindow;
548 ::Window aFocusWindow = pEnv->aWindow;
549
550 XSetICValues( maContext,
551 XNFocusWindow, aFocusWindow,
552 XNClientWindow, aClientWindow,
553 nullptr );
554
555 if( maClientData.aInputEv.mpTextAttr )
556 {
557 sendEmptyCommit(pFocusFrame);
558 // begin preedit again
559 vcl_sal::getSalDisplay(GetGenericUnixSalData())->SendInternalEvent( pFocusFrame, &maClientData.aInputEv, SalEvent::ExtTextInput );
560 }
561
562 XSetICFocus( maContext );
563 }
564 }
565
566 void
UnsetICFocus()567 SalI18N_InputContext::UnsetICFocus()
568 {
569
570 if ( mbUseable && (maContext != nullptr) )
571 {
572 // cancel an eventual event posted to begin preedit again
573 vcl_sal::getSalDisplay(GetGenericUnixSalData())->CancelInternalEvent( maClientData.pFrame, &maClientData.aInputEv, SalEvent::ExtTextInput );
574 maClientData.pFrame = nullptr;
575 XUnsetICFocus( maContext );
576 }
577 }
578
579 // multi byte input method only
580
581 void
EndExtTextInput()582 SalI18N_InputContext::EndExtTextInput()
583 {
584 if ( mbUseable && (maContext != nullptr) && maClientData.pFrame )
585 {
586 vcl::DeletionListener aDel( maClientData.pFrame );
587 // delete preedit in sal (commit an empty string)
588 sendEmptyCommit( maClientData.pFrame );
589 if( ! aDel.isDeleted() )
590 {
591 // mark previous preedit state again (will e.g. be sent at focus gain)
592 maClientData.aInputEv.mpTextAttr = maClientData.aInputFlags.data();
593 if( static_cast<X11SalFrame*>(maClientData.pFrame)->hasFocus() )
594 {
595 // begin preedit again
596 vcl_sal::getSalDisplay(GetGenericUnixSalData())->SendInternalEvent( maClientData.pFrame, &maClientData.aInputEv, SalEvent::ExtTextInput );
597 }
598 }
599 }
600 }
601
602 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
603