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
Display* sharedDisplay = NULL;
unsigned int referenceCount = 0;
XIM sharedXIM = NULL;
unsigned int referenceCountXIM = 0;
sf::Mutex mutex;
typedef std::map<std::string, Atom> AtomMap;
@ -85,6 +87,56 @@ void CloseDisplay(Display* 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)

View File

@ -55,6 +55,27 @@ Display* OpenDisplay();
////////////////////////////////////////////////////////////
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
///

View File

@ -101,7 +101,9 @@ namespace
Bool checkEvent(::Display*, XEvent* event, XPointer userData)
{
// 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
@ -803,7 +805,7 @@ WindowImplX11::~WindowImplX11()
// Close the input method
if (m_inputMethod)
XCloseIM(m_inputMethod);
CloseXIM(m_inputMethod);
// Close the connection with the X server
CloseDisplay(m_display);
@ -1607,7 +1609,7 @@ void WindowImplX11::initialize()
using namespace WindowsImplX11Impl;
// Create the input context
m_inputMethod = XOpenIM(m_display, NULL, NULL, NULL);
m_inputMethod = OpenXIM();
if (m_inputMethod)
{
@ -1845,27 +1847,31 @@ bool WindowImplX11::processEvent(XEvent& windowEvent)
// Close event
case ClientMessage:
{
static Atom wmProtocols = getAtom("WM_PROTOCOLS");
// Handle window manager protocol messages we support
if (windowEvent.xclient.message_type == wmProtocols)
// Input methods might want random ClientMessage events
if (!XFilterEvent(&windowEvent, None))
{
static Atom wmDeleteWindow = getAtom("WM_DELETE_WINDOW");
static Atom netWmPing = ewmhSupported() ? getAtom("_NET_WM_PING", true) : None;
static Atom wmProtocols = getAtom("WM_PROTOCOLS");
if ((windowEvent.xclient.format == 32) && (windowEvent.xclient.data.l[0]) == static_cast<long>(wmDeleteWindow))
// Handle window manager protocol messages we support
if (windowEvent.xclient.message_type == wmProtocols)
{
// Handle the WM_DELETE_WINDOW message
Event event;
event.type = Event::Closed;
pushEvent(event);
}
else if (netWmPing && (windowEvent.xclient.format == 32) && (windowEvent.xclient.data.l[0]) == static_cast<long>(netWmPing))
{
// Handle the _NET_WM_PING message, send pong back to WM to show that we are responsive
windowEvent.xclient.window = DefaultRootWindow(m_display);
static Atom wmDeleteWindow = getAtom("WM_DELETE_WINDOW");
static Atom netWmPing = ewmhSupported() ? getAtom("_NET_WM_PING", true) : None;
XSendEvent(m_display, DefaultRootWindow(m_display), False, SubstructureNotifyMask | SubstructureRedirectMask, &windowEvent);
if ((windowEvent.xclient.format == 32) && (windowEvent.xclient.data.l[0]) == static_cast<long>(wmDeleteWindow))
{
// Handle the WM_DELETE_WINDOW message
Event event;
event.type = Event::Closed;
pushEvent(event);
}
else if (netWmPing && (windowEvent.xclient.format == 32) && (windowEvent.xclient.data.l[0]) == static_cast<long>(netWmPing))
{
// Handle the _NET_WM_PING message, send pong back to WM to show that we are responsive
windowEvent.xclient.window = DefaultRootWindow(m_display);
XSendEvent(m_display, DefaultRootWindow(m_display), False, SubstructureNotifyMask | SubstructureRedirectMask, &windowEvent);
}
}
}
break;
@ -1904,7 +1910,7 @@ bool WindowImplX11::processEvent(XEvent& windowEvent)
if (m_inputContext)
{
Status status;
Uint8 keyBuffer[16];
Uint8 keyBuffer[64];
int length = Xutf8LookupString(
m_inputContext,
@ -1915,16 +1921,26 @@ bool WindowImplX11::processEvent(XEvent& windowEvent)
&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;
Utf8::decode(keyBuffer, keyBuffer + length, unicode, 0);
if (unicode != 0)
Uint8* iter = keyBuffer;
while (iter < keyBuffer + length)
{
Event textEvent;
textEvent.type = Event::TextEntered;
textEvent.text.unicode = unicode;
pushEvent(textEvent);
iter = Utf8::decode(iter, keyBuffer + length, unicode, 0);
if (unicode != 0)
{
Event textEvent;
textEvent.type = Event::TextEntered;
textEvent.text.unicode = unicode;
pushEvent(textEvent);
}
}
}
}