X11: fix XIM input method support

This commit is contained in:
Edgaru089 2021-12-07 21:29:35 +08:00 committed by Lukas Dürrenberger
parent e458f4651e
commit 2567551469
3 changed files with 117 additions and 28 deletions

View File

@ -40,6 +40,8 @@ namespace
// The shared display and its reference counter // The shared display and its reference counter
Display* sharedDisplay = NULL; Display* sharedDisplay = NULL;
unsigned int referenceCount = 0; unsigned int referenceCount = 0;
XIM sharedXIM = NULL;
unsigned int referenceCountXIM = 0;
sf::Mutex mutex; sf::Mutex mutex;
typedef std::map<std::string, Atom> AtomMap; typedef std::map<std::string, Atom> AtomMap;
@ -85,6 +87,56 @@ void CloseDisplay(Display* display)
XCloseDisplay(display); XCloseDisplay(display);
} }
////////////////////////////////////////////////////////////
XIM OpenXIM()
{
Lock lock(mutex);
assert(sharedDisplay != NULL);
if (referenceCountXIM == 0)
{
// Create a new XIM instance
// We need the default (environment) locale and X locale for opening
// the IM and properly receiving text
// First save the previous ones (this might be able to be written more elegantly?)
const char* p;
std::string prevLoc((p = setlocale(LC_ALL, NULL)) ? p : "");
std::string prevXLoc((p = XSetLocaleModifiers(NULL)) ? p : "");
// Set the locales from environment
setlocale(LC_ALL, "");
XSetLocaleModifiers("");
// Create the input context
sharedXIM = XOpenIM(sharedDisplay, NULL, NULL, NULL);
// Restore the previous locale
if (prevLoc.length() != 0)
setlocale(LC_ALL, prevLoc.c_str());
if (prevXLoc.length() != 0)
XSetLocaleModifiers(prevXLoc.c_str());
}
referenceCountXIM++;
return sharedXIM;
}
////////////////////////////////////////////////////////////
void CloseXIM(XIM xim)
{
Lock lock(mutex);
assert(xim == sharedXIM);
referenceCountXIM--;
if ((referenceCountXIM == 0) && (xim != NULL))
XCloseIM(xim);
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Atom getAtom(const std::string& name, bool onlyIfExists) Atom getAtom(const std::string& name, bool onlyIfExists)

View File

@ -55,6 +55,27 @@ Display* OpenDisplay();
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void CloseDisplay(Display* display); void CloseDisplay(Display* display);
////////////////////////////////////////////////////////////
/// \brief Get the shared XIM context for the Display
///
/// This function increments the reference count of the XIM context,
/// it must be matched with a call to CloseXIM.
///
/// It must be called with a display already opened.
///
/// \return XIM handle (a pointer) of the context
///
////////////////////////////////////////////////////////////
XIM OpenXIM();
////////////////////////////////////////////////////////////
/// \brief Release a reference to the shared XIM context
///
/// \param xim XIM context to release
///
////////////////////////////////////////////////////////////
void CloseXIM(XIM xim);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Get the atom with the specified name /// \brief Get the atom with the specified name
/// ///

View File

@ -101,7 +101,9 @@ namespace
Bool checkEvent(::Display*, XEvent* event, XPointer userData) Bool checkEvent(::Display*, XEvent* event, XPointer userData)
{ {
// Just check if the event matches the window // Just check if the event matches the window
return event->xany.window == reinterpret_cast< ::Window >(userData); // The input method sometimes sends ClientMessages with a different window ID,
// our event loop has to process them for the IM to work
return (event->xany.window == reinterpret_cast< ::Window >(userData)) || (event->type == ClientMessage);
} }
// Find the name of the current executable // Find the name of the current executable
@ -803,7 +805,7 @@ WindowImplX11::~WindowImplX11()
// Close the input method // Close the input method
if (m_inputMethod) if (m_inputMethod)
XCloseIM(m_inputMethod); CloseXIM(m_inputMethod);
// Close the connection with the X server // Close the connection with the X server
CloseDisplay(m_display); CloseDisplay(m_display);
@ -1607,7 +1609,7 @@ void WindowImplX11::initialize()
using namespace WindowsImplX11Impl; using namespace WindowsImplX11Impl;
// Create the input context // Create the input context
m_inputMethod = XOpenIM(m_display, NULL, NULL, NULL); m_inputMethod = OpenXIM();
if (m_inputMethod) if (m_inputMethod)
{ {
@ -1844,6 +1846,9 @@ bool WindowImplX11::processEvent(XEvent& windowEvent)
// Close event // Close event
case ClientMessage: case ClientMessage:
{
// Input methods might want random ClientMessage events
if (!XFilterEvent(&windowEvent, None))
{ {
static Atom wmProtocols = getAtom("WM_PROTOCOLS"); static Atom wmProtocols = getAtom("WM_PROTOCOLS");
@ -1868,6 +1873,7 @@ bool WindowImplX11::processEvent(XEvent& windowEvent)
XSendEvent(m_display, DefaultRootWindow(m_display), False, SubstructureNotifyMask | SubstructureRedirectMask, &windowEvent); XSendEvent(m_display, DefaultRootWindow(m_display), False, SubstructureNotifyMask | SubstructureRedirectMask, &windowEvent);
} }
} }
}
break; break;
} }
@ -1904,7 +1910,7 @@ bool WindowImplX11::processEvent(XEvent& windowEvent)
if (m_inputContext) if (m_inputContext)
{ {
Status status; Status status;
Uint8 keyBuffer[16]; Uint8 keyBuffer[64];
int length = Xutf8LookupString( int length = Xutf8LookupString(
m_inputContext, m_inputContext,
@ -1915,10 +1921,19 @@ bool WindowImplX11::processEvent(XEvent& windowEvent)
&status &status
); );
if (length > 0) if (status == XBufferOverflow)
err() << "A TextEntered event has more than 64 bytes of UTF-8 input, and "
"has been discarded\nThis means either you have typed a very long string "
"(more than 20 chars), or your input method is broken in obscure ways." << std::endl;
else if (status == XLookupChars)
{ {
// There might be more than 1 characters in this event,
// so we must iterate it
Uint32 unicode = 0; Uint32 unicode = 0;
Utf8::decode(keyBuffer, keyBuffer + length, unicode, 0); Uint8* iter = keyBuffer;
while (iter < keyBuffer + length)
{
iter = Utf8::decode(iter, keyBuffer + length, unicode, 0);
if (unicode != 0) if (unicode != 0)
{ {
Event textEvent; Event textEvent;
@ -1928,6 +1943,7 @@ bool WindowImplX11::processEvent(XEvent& windowEvent)
} }
} }
} }
}
else else
#endif #endif
{ {