Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

X11: fix XIM input method support #1850

Merged
merged 1 commit into from Dec 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/SFML/Window/Unix/Display.cpp
Expand Up @@ -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;
Expand Down Expand Up @@ -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--;
Edgaru089 marked this conversation as resolved.
Show resolved Hide resolved

if ((referenceCountXIM == 0) && (xim != NULL))
XCloseIM(xim);
}

////////////////////////////////////////////////////////////
Atom getAtom(const std::string& name, bool onlyIfExists)
Expand Down
21 changes: 21 additions & 0 deletions src/SFML/Window/Unix/Display.hpp
Expand Up @@ -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
///
Expand Down
72 changes: 44 additions & 28 deletions src/SFML/Window/Unix/WindowImplX11.cpp
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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);
}
}
}
}
Expand Down