Linux/SDL2 - IME support for composing Japanese, Chinese, etc. input. Tested with ibus/mozc giving over-the-top editing with candidate clause selection, Media pages in the internal web browser do not yet have accurate input placement

master
Zi Ree 2022-09-14 11:19:51 +02:00
parent d42936ee6f
commit a5cb6dab38
8 changed files with 244 additions and 14 deletions

View File

@ -57,6 +57,12 @@
// Imported globals
//
// <FS:Zi> IME - International input compositing, i.e. for Japanese / Chinese text input
#if LL_SDL2
extern LLControlGroup gSavedSettings;
#endif
// </FS:Zi>
//
// Constants
//
@ -2164,6 +2170,12 @@ void LLLineEditor::draw()
ime_pos.mX = (S32) (ime_pos.mX * LLUI::getScaleFactor().mV[VX]);
ime_pos.mY = (S32) (ime_pos.mY * LLUI::getScaleFactor().mV[VY]);
// <FS:Zi> IME - International input compositing, i.e. for Japanese / Chinese text input
#if LL_SDL2
static LLCachedControl<S32> sdl2_ime_default_vertical_offset(*LLControlGroup::getInstance("Global"), "SDL2IMEDefaultVerticalOffset");
ime_pos.mY += sdl2_ime_default_vertical_offset;
#endif
// </FS:Zi>
getWindow()->setLanguageTextInput( ime_pos );
}
}
@ -2313,12 +2325,21 @@ void LLLineEditor::setFocus( BOOL new_state )
if (new_state)
{
// <FS:Zi> IME - International input compositing, i.e. for Japanese / Chinese text input
#if LL_SDL2
// Linux/SDL2 doesn't currently allow to disable IME, so we remove the restrictions on
// password entry fields and prevalidated input fields. Otherwise those fields would
// be completely inaccessible.
getWindow()->allowLanguageTextInput(this, true);
#else
// </FS:Zi>
// Allow Language Text Input only when this LineEditor has
// no prevalidate function attached. This criterion works
// fine on 1.15.0.2, since all prevalidate func reject any
// non-ASCII characters. I'm not sure on future versions,
// however.
getWindow()->allowLanguageTextInput(this, mPrevalidateFunc == NULL);
#endif // <FS:Zi>
}
}
@ -2498,7 +2519,16 @@ void LLLineEditor::updateAllowingLanguageInput()
// test app, no window available
return;
}
// <FS:Zi> IME - International input compositing, i.e. for Japanese / Chinese text input
#if LL_SDL2
// Linux/SDL2 doesn't currently allow to disable IME, so we remove the restrictions on
// password entry fields and prevalidated input fields. Otherwise those fields would
// be completely inaccessible.
if (hasFocus() && !mReadOnly)
#else
// </FS:Zi>
if (hasFocus() && !mReadOnly && !mDrawAsterixes && mPrevalidateFunc == NULL)
#endif // <FS:Zi>
{
window->allowLanguageTextInput(this, TRUE);
}

View File

@ -50,6 +50,12 @@
#include "fsregistrarutils.h"
// <FS:Zi> IME - International input compositing, i.e. for Japanese / Chinese text input
#if LL_SDL2
extern LLControlGroup gSavedSettings;
#endif
// </FS:Zi>
const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds
const S32 CURSOR_THICKNESS = 2;
const F32 TRIPLE_CLICK_INTERVAL = 0.3f; // delay between double and triple click.
@ -645,6 +651,12 @@ void LLTextBase::drawCursor()
ime_pos.mX = (S32) (ime_pos.mX * LLUI::getScaleFactor().mV[VX]);
ime_pos.mY = (S32) (ime_pos.mY * LLUI::getScaleFactor().mV[VY]);
// <FS:Zi> IME - International input compositing, i.e. for Japanese / Chinese text input
#if LL_SDL2
static LLCachedControl<S32> sdl2_ime_default_vertical_offset(*LLControlGroup::getInstance("Global"), "SDL2IMEDefaultVerticalOffset");
ime_pos.mY += sdl2_ime_default_vertical_offset;
#endif
// </FS:Zi>
getWindow()->setLanguageTextInput( ime_pos );
}
}

View File

@ -39,6 +39,7 @@
#include "llstring.h"
#include "lldir.h"
#include "llfindlocale.h"
#include "llframetimer.h"
#ifdef LL_GLIB
#include <glib.h>
@ -390,6 +391,10 @@ LLWindowSDL::LLWindowSDL(LLWindowCallbacks* callbacks,
mIsMinimized = -1;
mFSAASamples = fsaa_samples;
// IME - International input compositing, i.e. for Japanese / Chinese text input
// Preeditor means here the actual XUI input field currently in use
mPreeditor = nullptr;
#if LL_X11
mSDL_XWindowID = None;
mSDL_Display = NULL;
@ -654,6 +659,10 @@ BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, B
SDL_SetHint( SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0" );
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
// IME - International input compositing, i.e. for Japanese / Chinese text input
// Request the IME interface to show over-the-top compositing while typing
SDL_SetHint( SDL_HINT_IME_INTERNAL_EDITING, "1");
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO ) < 0 )
{
LL_INFOS() << "sdl_init() failed! " << SDL_GetError() << LL_ENDL;
@ -1758,6 +1767,7 @@ void LLWindowSDL::gatherInput()
static int rightClick = 0;
static Uint32 lastLeftDown = 0;
static Uint32 lastRightDown = 0;
static U64 previousTextinputTime = 0;
SDL_Event event;
// Handle all outstanding SDL events
@ -1796,9 +1806,10 @@ void LLWindowSDL::gatherInput()
else
handleUnicodeUTF16(key, mKeyModifiers);
}
previousTextinputTime = LLFrameTimer::getTotalTime();
break;
}
case SDL_KEYDOWN:
mKeyVirtualKey = event.key.keysym.sym;
mKeyModifiers = event.key.keysym.mod;
@ -1810,6 +1821,19 @@ void LLWindowSDL::gatherInput()
mKeyVirtualKey = SDLK_RETURN;
}
if (mKeyVirtualKey == SDLK_RETURN)
{
// block spurious enter key events that break up IME entered lines in teh wrong places
U64 eventTimeDiff = LLFrameTimer::getTotalTime() - previousTextinputTime;
previousTextinputTime = 0;
if (eventTimeDiff < 20000)
{
LL_INFOS() << "SDL_KEYDOWN(SDLK_RETURN) event came too fast after SDL_TEXTINPUT, blocked - Time: " << eventTimeDiff << LL_ENDL;
break;
}
}
gKeyboard->handleKeyDown(mKeyVirtualKey, mKeyModifiers );
// <FS:ND> Slightly hacky :| To make the viewer honor enter (eg to accept form input) we've to not only send handleKeyDown but also send a
@ -2646,4 +2670,51 @@ void LLWindowSDL::toggleVSync(bool enable_vsync)
}
// </FS:Zi>
// IME - International input compositing, i.e. for Japanese / Chinese text input
// Put the IME window at the right place (near current text input).
// Point coordinates should be the top of the current text line.
void LLWindowSDL::setLanguageTextInput(const LLCoordGL& position)
{
LLCoordWindow win_pos;
convertCoords( position, &win_pos );
SDL_Rect r;
r.x = win_pos.mX;
r.y = win_pos.mY;
r.w = 500;
r.h = 16;
SDL_SetTextInputRect(&r);
}
// IME - International input compositing, i.e. for Japanese / Chinese text input
void LLWindowSDL::allowLanguageTextInput(LLPreeditor *preeditor, BOOL b)
{
if (preeditor != mPreeditor && !b)
{
// This condition may occur with a call to
// setEnabled(BOOL) from LLTextEditor or LLLineEditor
// when the control is not focused.
// We need to silently ignore the case so that
// the language input status of the focused control
// is not disturbed.
return;
}
// Take care of old and new preeditors.
if (preeditor != mPreeditor || !b)
{
mPreeditor = (b ? preeditor : nullptr);
}
if (b)
{
SDL_StartTextInput();
}
else
{
SDL_StopTextInput();
}
}
#endif // LL_SDL

View File

@ -128,6 +128,9 @@ public:
/*virtual*/ void *getPlatformWindow();
/*virtual*/ void bringToFront();
/*virtual*/ void allowLanguageTextInput(LLPreeditor* preeditor, BOOL b);
/*virtual*/ void setLanguageTextInput(const LLCoordGL& pos);
/*virtual*/ void spawnWebBrowser(const std::string& escaped_url, bool async);
/*virtual*/ void openFile(const std::string& file_name);
@ -201,6 +204,7 @@ protected:
SDL_Surface* mSurface;
SDL_GLContext mContext;
SDL_Cursor* mSDLCursors[UI_CURSOR_COUNT];
LLPreeditor* mPreeditor;
std::string mWindowTitle;
double mOriginalAspectRatio;

View File

@ -26203,5 +26203,38 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Value</key>
<integer>0</integer>
</map>
<key>SDL2IMEDefaultVerticalOffset</key>
<map>
<key>Comment</key>
<string>Default vertical offset to apply to the international input method editor for Japanese, Chinese, etc.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>S32</string>
<key>Value</key>
<integer>18</integer>
</map>
<key>SDL2IMEChatHistoryVerticalOffset</key>
<map>
<key>Comment</key>
<string>Chat History: Vertical offset to apply to the international input method editor for Japanese, Chinese, etc.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>S32</string>
<key>Value</key>
<integer>14</integer>
</map>
<key>SDL2IMEMediaVerticalOffset</key>
<map>
<key>Comment</key>
<string>Media: Vertical offset to apply to the international input method editor for Japanese, Chinese, etc.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>S32</string>
<key>Value</key>
<integer>-4</integer>
</map>
</map>
</llsd>

View File

@ -68,6 +68,10 @@
#include "llviewermenu.h"
#include "llviewernetwork.h"
#if LL_SDL2
#include "llwindow.h"
#endif
#include "fscommon.h"
#include "llchatentry.h"
#include "llfocusmgr.h"
@ -1210,6 +1214,50 @@ FSChatHistory::~FSChatHistory()
this->clear();
}
void FSChatHistory::updateChatInputLine()
{
if(!mChatInputLine)
{
// get our focus root
LLUICtrl* focusRoot=findRootMostFocusRoot();
if(focusRoot)
{
// focus on the next item that is a text input control
focusRoot->focusNextItem(true);
// remember the control's pointer if it really is a LLLineEditor
mChatInputLine = dynamic_cast<LLChatEntry*>(gFocusMgr.getKeyboardFocus());
}
}
}
#if LL_SDL2
void FSChatHistory::setFocus(BOOL b)
{
LLTextEditor::setFocus(b);
// IME - International input compositing, i.e. for Japanese / Chinese text input
updateChatInputLine();
if (b && mChatInputLine)
{
// Make sure the IME is in the right place, on top of the input line
LLRect screen_pos = mChatInputLine->calcScreenRect();
LLCoordGL ime_pos(screen_pos.mLeft, screen_pos.mBottom + gSavedSettings.getS32("SDL2IMEChatHistoryVerticalOffset"));
// shift by a few pixels so the IME doesn't pop to the left side when the input
// field is very close to the left edge
ime_pos.mX = (S32) (ime_pos.mX * LLUI::getScaleFactor().mV[VX]) + 5;
ime_pos.mY = (S32) (ime_pos.mY * LLUI::getScaleFactor().mV[VY]);
getWindow()->setLanguageTextInput(ime_pos);
}
// this floater is not an LLPreeditor but we are only interested in the pointer anyway
// so hopefully we will get away with this
getWindow()->allowLanguageTextInput((LLPreeditor*) this, true);
}
#endif
void FSChatHistory::initFromParams(const FSChatHistory::Params& p)
{
// initialize the LLTextEditor base class first ... -Zi
@ -1794,19 +1842,8 @@ BOOL FSChatHistory::handleUnicodeCharHere(llwchar uni_char)
return LLTextEditor::handleUnicodeCharHere(uni_char);
}
// we don't know which is our chat input line yet
if(!mChatInputLine)
{
// get our focus root
LLUICtrl* focusRoot=findRootMostFocusRoot();
if(focusRoot)
{
// focus on the next item that is a text input control
focusRoot->focusNextItem(true);
// remember the control's pointer if it really is a LLLineEditor
mChatInputLine = dynamic_cast<LLChatEntry*>(gFocusMgr.getKeyboardFocus());
}
}
// we might not know which is our chat input line yet
updateChatInputLine();
// do we know our chat input line now?
if(mChatInputLine)

View File

@ -96,8 +96,17 @@ class FSChatHistory : public LLTextEditor // <FS:Zi> FIRE-8600: TAB out of chat
*/
LLView* getHeader(const LLChat& chat,const LLStyle::Params& style_params, const LLSD& args);
// try to fill in mChatInputLine
void updateChatInputLine();
public:
~FSChatHistory();
#if LL_SDL2
// IME - International input compositing, i.e. for Japanese / Chinese text input
/* virtual */ void setFocus(BOOL b);
#endif
LLSD getValue() const;
void initFromParams(const Params&);

View File

@ -48,6 +48,12 @@
#include "llviewermenu.h"
#include "llviewermenufile.h" // LLFilePickerThread
// <FS:Zi> IME - International input compositing, i.e. for Japanese / Chinese text input
#if LL_SDL2
#include "llwindow.h"
#endif
// </FS:Zi>
// linden library includes
#include "llfocusmgr.h"
#include "llsdutil.h"
@ -411,6 +417,34 @@ void LLMediaCtrl::onFocusLost()
// This might go away later.
void LLMediaCtrl::setFocus(BOOL b)
{
// <FS:Zi> IME - International input compositing, i.e. for Japanese / Chinese text input
#if LL_SDL2
// IME - International input compositing, i.e. for Japanese / Chinese text input
// Caveat: we currently don't know the position of the input cursor inside the
// media control box, so the IME will pop up somewhere at the top instead,
// which is not ideal and needs more research
if (b)
{
// Make sure the IME is in the right place, on top of the input line
LLRect screen_pos = calcScreenRect();
LLCoordGL ime_pos(screen_pos.mLeft, screen_pos.mTop + gSavedSettings.getS32("SDL2IMEMediaVerticalOffset"));
// shift by a few pixels so the IME doesn't pop to the left side when the nedia
// control is very close to the left edge
ime_pos.mX = (S32) (ime_pos.mX * LLUI::getScaleFactor().mV[VX]) + 5;
ime_pos.mY = (S32) (ime_pos.mY * LLUI::getScaleFactor().mV[VY]);
getWindow()->setLanguageTextInput(ime_pos);
}
// this floater is not an LLPreeditor but we are only interested in the pointer anyway
// so hopefully we will get away with this
getWindow()->allowLanguageTextInput((LLPreeditor*) this, b);
#endif
// </FS:Zi>
if (b)
{
onFocusReceived();