diff --git a/src/SFML/Window/Unix/Display.cpp b/src/SFML/Window/Unix/Display.cpp index 144fe782..730f7227 100644 --- a/src/SFML/Window/Unix/Display.cpp +++ b/src/SFML/Window/Unix/Display.cpp @@ -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 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) diff --git a/src/SFML/Window/Unix/Display.hpp b/src/SFML/Window/Unix/Display.hpp index 81a437f4..7dc0cf70 100644 --- a/src/SFML/Window/Unix/Display.hpp +++ b/src/SFML/Window/Unix/Display.hpp @@ -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 /// diff --git a/src/SFML/Window/Unix/WindowImplX11.cpp b/src/SFML/Window/Unix/WindowImplX11.cpp index 569f5f15..a18e2eee 100644 --- a/src/SFML/Window/Unix/WindowImplX11.cpp +++ b/src/SFML/Window/Unix/WindowImplX11.cpp @@ -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(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(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(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(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); + } } } }