OS/2 codes: How to support DBCS #5 Level 3 application(Full-IME)

So far, we've found the way to support DBCS without consideration of IME itself. They are called level 1 application. Level 1 applications are useful. However, they still lack something. Because, a conversion window is not integrated to a working window. For example, let's consider a word processor program. On level 1 application, a size and a type of a font of a conversion window are different from ones in a working window. This looks ugly and uncomfortable. In addition, level 1 applications use over-the-spot input type. That is, a conversion window shows up over a underlying text. Users cannot see the texts under the conversion window until a conversion is completed.

In this article, let's find to make level 3 applications, which use IME fully.

Making Level 3 Applications


To make level 3 applications, you neede OS/2 ToolKit v4.5 or later. And when linking a program, you need to link against os2im.lib. Make sure to add os2tk45/h to C_INCLUDE_PATH and to add os2tk45/lib to LIBRARY_PATH.

However, if you link os2im.lib to your programs, then it will be able to be excuted only on DBCS OS/2. Because they will load OS2IM.DLL at startup, however, OS2IM.DLL is shipped only with DBCS OS/2.

To avoid this, it is needed to load OS2IM.DLL dynamically. Here is the code.

OS2IM dynamic loader :

Usage is simple. If you use gcc, just link im32.c instead of os2im.lib. Or if you use other compilers than gcc, then you should include im32.h and call im32Init() at startup and im32Term() at exit. Regardless compilers, if you want to use im32Inited(), then you should include im32.h.

Using OS/2 32 bits Input Method


OS/2 IM working principles are not difference from other PM modules. It provides messages and APIs to communicate with and to control IME.

OS/2 IM Messages


Let's see messages first. OS/2 IM provides three messages. They are

  • WM_IMECONTROL
  • WM_IMEREQUEST
  • WM_IMENOTIFY

Out of this, WM_IMEREQUEST and WM_IMENOTIFY are used generally, and WM_IMEREQUEST is more importatnt. Actually, just WM_IMEREQUEST is enough to make a general level 3 program. In this article, we'll look WM_IMEREQUEST only.

WM_IMEREQUEST


This message is sent to the application window for the notification of the events. And this message has many request types passed in [mp1]. Out of them, types which should be considerd are only one, IMR_CONVRESULT. Of course, IMR_CONVRESULT has sub-informations passed in [mp2]. However, it's ok to consider just two message,

  • IMR_RESULT_RESULTSTRING
  • IMR_CONV_CONVERSIONSTRING

After all, the logic is like the following.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    case WM_IMEREQUEST :
        if( LONGFROMMP( mp1 ) == IMR_CONVRESULT )
        {
            if( LONGFROMMP( mp2 ) == IMR_RESULT_RESULTSTRING )
            {
                return GetResultString( hwnd );
            }
            else if( LONGFROMMP( mp2 ) == IMR_CONV_CONVERSIONSTRING )
            {
                return GetConversionString( hwnd );
            }
          
            return FALSE;
        }

        /* Pass to a default window procedure */
        return WinDefWindowProc( hwnd, WM_IMEREQUEST, mp1, mp2 );



* Note : IMR_RESULT_RESULTSTRING is checked and processed before IMR_CONV_CONVERSIONSTRING. Do not process both them simultaneously.

Of course, as well as this, you should process WM_CHAR and WM_QUERYCONVERTPOS.

All done for messages. Now, see APIs part.

OS/2 IM APIs


As you see, processing parts of IMR_RESULT_RESULTSTRING and IMR_CONV_CONVERSIONSTRING are not implemented. Right here, APIs are needed. Out of APIs, most important things are

  • ImGetInstance()
  • ImReleaseInstance() 
  • ImGetConversionString()
  • ImGetResultString()
  • ImRequestIME()

ImGetInstance() is used to get a IM instance for a window. And ImReleaseInstance() releases it. As you know from their name, ImGetConversionString() get a conversion string from IME and ImGetResultString() get a result string from IME. In addition, they get additional informations as well as a string from IME. ImRequestIME() requests some tasks to IME.

Usage of OS/2 IM APIs is very typical. Do like this.

1
2
3
4
5
6
7
8
HIMI himi;

if( ImGetInstance( hwnd, &himi ) == 0 )
{
    /* do something */

    ImReleaseInstance( hwnd, himi );
}


Now, see GetResultString() and GetConversionString().

GetResultString()


GetResulString() gets a result string with ImGetResultString() from IME, and sends the string to WM_CHAR. Of course, it's possible to insert the string to internal buffer directly without sending WM_CHAR.

When getting a result string from ImGetResultString(), you should prepare a enough buffer. For this, ImGetResultString() provides a size of enough buffer to hold a result string. Here is a prototype of ImGetResultString() from os2im.h.

APIRET APIENTRY ImGetResultString( HIMI himi, ULONG ulIndex,
                                   PVOID pBuf, PULONG pulBufLen );

[ulIndex] is an index of information to query. [pBuf] is a buffer to hold an output. [pulBufLen] is a pointer to a buffer size in bytes.

If you set [*pulBufLen] to 0, then you can get an enough buffer size. So you should always query an enough buffer size before getting a string actually.

Out of many index, IMR_RESULT_RESULTSTRING is needed. Actual codes for getting a result string are like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if( ImGetInstance( hwnd, &himi ) == 0 )
{
    ulBufLen = 0;

    /* Get an enough buffer size */
    if( ImGetResultString( himi, IMR_RESULT_RESULTSTRING, NULL,
                           &ulBufLen ) == 0 )
    {
        pBuf = calloc( 1, ulBufLen + 1 );
  
        /* Get an actual result string */
        if( ImGetResultString( himi, IMR_RESULT_RESULTSTRING, pBuf,
                               &ulBufLen ) != 0 )
        {
            ulBufLen = 0;    /* process an error silently */
        }

        ProcessResultString( hwnd, pBuf, ulBufLen );
                            
        free( pBuf );
   
        ImReleaseInstance( hwnd, himi );
    }
}    


ProcessResultString() sends [pBuf] to [hwnd] via WM_CHAR like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
    for (i = 0; i < ulBufLen; i++ ) {
        if( IsDBCSLeadByte( pBuf[ i ] ))
        {
            WinSendMsg( hwnd , WM_CHAR , MPFROMSH2CH( KC_CHAR, 0, 0 ),
                        MPFROM2SHORT( MAKEUSHORT(( UCHAR )pBuf[ i ],
                                                 ( UCHAR )pBuf[ i + 1 ]),
                                      0 ));
            i++;
        } else {
            WinSendMsg( hwnd , WM_CHAR, MPFROMSH2CH( KC_CHAR, 0, 0 ),
                        MPFROM2SHORT( MAKEUSHORT(( UCHAR )pBuf[ i ], 0 ), 0 ));
        }
    }


Now, see GetConversionString(). This is rather complicated.

GetConversionString()


GetConversionString() gets a conversion string with ImGetConversionString() from IME. Here is a prototype of ImGetConversionString().

APIRET APIENTRY ImGetConversionString( HIMI himi, ULONG ulIndex,
                                       PVOID pBuf, PULONG pulBufLen );

Parameters are same as ImGetResultString(). And usage is same, too. First get an enough buffer size, and get a real string. However, you should get other informations with ImGetConversionString() as well. Let's see what they are.

  • IMR_CONV_CONVERSIONSTRING
  • IMR_CONV_CONVERSIONATTR
  • IMR_CONV_CURSORPOS

Flow is like this,

  1. Get a conversion string with IMR_CONV_CONVERSIONSTRING
  2. Get attrs of a conversion string with IMR_CONV_CONVERSIONATTR
  3. Get a cursor position in a conversion string with IMR_CONV_CURSORPOS

Attrs of a conversion string is an array of an attribute corresponding to a character at same position  of a conversion string. They are

  • CP_ATTR_INPUT : The chracters are now on input, not yet converted
  • CP_ATTR_TARGET_CONVERTED : The chracters have been converted and are now selected
  • CP_ATTR_CONVERTED : The characters have been converted
  • CP_ATTR_TARGET_NOTCONVERTED : The characters are selected and not yet converted
  • CP_ATTR_INPUT_ERROR : The characters are invalid. IME can't convert them

Frankly, I don't understand what they are fully. Especially, CP_ATTR_TARGET_CONVERTED and CP_ATTR_TARGET_NOTCONVERTED. I can't test them because Korean IME does not support attributes at all. In fact, Koean IME does not need those. However, guessing from documents and other corresponding parts of Windows, at least CP_ATTR_TARGET_CONVERTED should be distinguished from others.

Requesting to IME


Sometimes, there is a time when asking something to IME. For example, a conversion should be canceled or completed when users click mouse buttons. ImRequestIME() does this. It's prototype is

APIRET APIENTRY ImRequestIME( HIMI himi, ULONG ulAction,
                              ULONG ulIndex, ULONG ulValue );

To cancel or to complete a conversion, set [ulAction] to REQ_CONVERSIONSTRING, [ulIndex] to CNV_CANCEL or CNV_COMPLETE and [ulValue] to 0.


Here is a simple line editor supporting full-IME.


To build, do like this

  • gcc im32-test.c im32.c

Finally, for details, see OS/2 32 bit Input Method reference in INF.





댓글

이 블로그의 인기 게시물

토렌트: < 왕좌의 게임 > 시즌 1 ~ 시즌 8 완결편 마그넷

토렌트: NGC < 코스모스 > 우리말 더빙 전편(1편~13편) 마그넷

토렌트: < 스타워즈 > Ep.1 ~ Ep.6 마그넷