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