diff options
author | James Cowgill <james410@cowgill.org.uk> | 2015-05-08 21:14:39 +0100 |
---|---|---|
committer | James Cowgill <james410@cowgill.org.uk> | 2015-05-08 21:14:39 +0100 |
commit | ebd1b636e5bf0f8fa6d210690582757e8b47f141 (patch) | |
tree | 02dc3aacf1c6f351154432247be0b4347fb14330 /src/SFML/Window/Unix | |
parent | fa21c65d0c764705cfc377bf0d0de08fac26874e (diff) |
Imported Upstream version 2.3+dfsg
Diffstat (limited to 'src/SFML/Window/Unix')
-rw-r--r-- | src/SFML/Window/Unix/Display.cpp | 278 | ||||
-rw-r--r-- | src/SFML/Window/Unix/Display.hpp | 79 | ||||
-rw-r--r-- | src/SFML/Window/Unix/GlxContext.cpp | 509 | ||||
-rw-r--r-- | src/SFML/Window/Unix/GlxContext.hpp | 25 | ||||
-rw-r--r-- | src/SFML/Window/Unix/GlxExtensions.cpp | 197 | ||||
-rw-r--r-- | src/SFML/Window/Unix/GlxExtensions.hpp | 203 | ||||
-rw-r--r-- | src/SFML/Window/Unix/GlxExtensions.txt | 11 | ||||
-rw-r--r-- | src/SFML/Window/Unix/InputImpl.cpp | 442 | ||||
-rw-r--r-- | src/SFML/Window/Unix/InputImpl.hpp | 2 | ||||
-rw-r--r-- | src/SFML/Window/Unix/JoystickImpl.cpp | 568 | ||||
-rw-r--r-- | src/SFML/Window/Unix/JoystickImpl.hpp | 7 | ||||
-rw-r--r-- | src/SFML/Window/Unix/ScopedXcbPtr.hpp | 102 | ||||
-rw-r--r-- | src/SFML/Window/Unix/ScopedXcbPtr.inl | 72 | ||||
-rw-r--r-- | src/SFML/Window/Unix/SensorImpl.cpp | 2 | ||||
-rw-r--r-- | src/SFML/Window/Unix/SensorImpl.hpp | 2 | ||||
-rw-r--r-- | src/SFML/Window/Unix/VideoModeImpl.cpp | 262 | ||||
-rw-r--r-- | src/SFML/Window/Unix/WindowImplX11.cpp | 2361 | ||||
-rw-r--r-- | src/SFML/Window/Unix/WindowImplX11.hpp | 135 |
18 files changed, 4124 insertions, 1133 deletions
diff --git a/src/SFML/Window/Unix/Display.cpp b/src/SFML/Window/Unix/Display.cpp index aad1b26..e537224 100644 --- a/src/SFML/Window/Unix/Display.cpp +++ b/src/SFML/Window/Unix/Display.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library -// Copyright (C) 2007-2014 Laurent Gomila (laurent.gom@gmail.com) +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the use of this software. @@ -27,8 +27,12 @@ //////////////////////////////////////////////////////////// #include <SFML/System/Err.hpp> #include <SFML/Window/Unix/Display.hpp> +#include <SFML/Window/Unix/ScopedXcbPtr.hpp> +#include <X11/keysym.h> #include <cassert> #include <cstdlib> +#include <algorithm> +#include <map> namespace @@ -36,6 +40,182 @@ namespace // The shared display and its reference counter Display* sharedDisplay = NULL; unsigned int referenceCount = 0; + + typedef std::map<std::string, xcb_atom_t> AtomMap; + AtomMap atoms; + + bool mapBuilt = false; + xcb_keycode_t firstKeycode = 255; + xcb_keycode_t lastKeycode = 0; + + // We use a simple array instead of a map => constant time lookup + // xcb_keycode_t can only contain 256 distinct values + xcb_keysym_t keysymMap[256]; + + xcb_keysym_t keysymToLower(xcb_keysym_t keysym) + { + switch(keysym >> 8) + { + // Latin 1 + case 0: + { + if ((keysym >= XK_A) && (keysym <= XK_Z)) + return keysym + (XK_a - XK_A); + else if ((keysym >= XK_Agrave) && (keysym <= XK_Odiaeresis)) + return keysym + (XK_agrave - XK_Agrave); + else if ((keysym >= XK_Ooblique) && (keysym <= XK_Thorn)) + return keysym + (XK_oslash - XK_Ooblique); + break; + } + + // Latin 2 + case 1: + { + if (keysym == XK_Aogonek) + return XK_aogonek; + else if (keysym >= XK_Lstroke && keysym <= XK_Sacute) + return keysym + (XK_lstroke - XK_Lstroke); + else if (keysym >= XK_Scaron && keysym <= XK_Zacute) + return keysym + (XK_scaron - XK_Scaron); + else if (keysym >= XK_Zcaron && keysym <= XK_Zabovedot) + return keysym + (XK_zcaron - XK_Zcaron); + else if (keysym >= XK_Racute && keysym <= XK_Tcedilla) + return keysym + (XK_racute - XK_Racute); + break; + } + + // Latin 3 + case 2: + { + if (keysym >= XK_Hstroke && keysym <= XK_Hcircumflex) + return keysym + (XK_hstroke - XK_Hstroke); + else if (keysym >= XK_Gbreve && keysym <= XK_Jcircumflex) + return keysym + (XK_gbreve - XK_Gbreve); + else if (keysym >= XK_Cabovedot && keysym <= XK_Scircumflex) + return keysym + (XK_cabovedot - XK_Cabovedot); + break; + } + + // Latin 4 + case 3: + { + if (keysym >= XK_Rcedilla && keysym <= XK_Tslash) + return keysym + (XK_rcedilla - XK_Rcedilla); + else if (keysym == XK_ENG) + return XK_eng; + else if (keysym >= XK_Amacron && keysym <= XK_Umacron) + return keysym + (XK_amacron - XK_Amacron); + break; + } + + // Cyrillic + case 6: + { + if (keysym >= XK_Serbian_DJE && keysym <= XK_Serbian_DZE) + return keysym - (XK_Serbian_DJE - XK_Serbian_dje); + else if (keysym >= XK_Cyrillic_YU && keysym <= XK_Cyrillic_HARDSIGN) + return keysym - (XK_Cyrillic_YU - XK_Cyrillic_yu); + break; + } + + // Greek + case 7: + { + if (keysym >= XK_Greek_ALPHAaccent && keysym <= XK_Greek_OMEGAaccent) + return keysym + (XK_Greek_alphaaccent - XK_Greek_ALPHAaccent); + else if (keysym >= XK_Greek_ALPHA && keysym <= XK_Greek_OMEGA) + return keysym + (XK_Greek_alpha - XK_Greek_ALPHA); + break; + } + + // Armenian + case 0x14: + { + if (keysym >= XK_Armenian_AYB && keysym <= XK_Armenian_fe) { + return (keysym | 1); + } + break; + } + + default: + { + break; + } + } + + return keysym; + } + + void buildMap() + { + // Open a connection with the X server + xcb_connection_t* connection = sf::priv::OpenConnection(); + + firstKeycode = xcb_get_setup(connection)->min_keycode; + lastKeycode = xcb_get_setup(connection)->max_keycode; + + sf::priv::ScopedXcbPtr<xcb_generic_error_t> error(NULL); + + sf::priv::ScopedXcbPtr<xcb_get_keyboard_mapping_reply_t> keyboardMapping(xcb_get_keyboard_mapping_reply( + connection, + xcb_get_keyboard_mapping( + connection, + firstKeycode, + lastKeycode - firstKeycode + 1 + ), + &error + )); + + sf::priv::CloseConnection(connection); + + if (error || !keyboardMapping) + { + sf::err() << "Failed to get keyboard mapping" << std::endl; + return; + } + + uint8_t keysymsPerKeycode = keyboardMapping->keysyms_per_keycode; + + if (!keysymsPerKeycode) + { + sf::err() << "Error: No keysyms per keycode" << std::endl; + return; + } + + const xcb_keysym_t* keysyms = xcb_get_keyboard_mapping_keysyms(keyboardMapping.get()); + + if (!keysyms) + { + sf::err() << "Failed to get keyboard mapping keysyms" << std::endl; + return; + } + + xcb_keycode_t range = lastKeycode - firstKeycode + 1; + + std::fill(keysymMap, keysymMap + 256, XK_VoidSymbol); + + for (xcb_keycode_t i = firstKeycode; ; ++i) + { + const xcb_keysym_t* keysym = &keysyms[(i - firstKeycode) * keysymsPerKeycode]; + + if ((keysymsPerKeycode == 1) || (keysym[1] == XCB_NO_SYMBOL)) + { + keysymMap[i] = keysymToLower(keysym[0]); + + if (i == lastKeycode) + break; + + continue; + } + + keysymMap[i] = keysym[0]; + + if (i == lastKeycode) + break; + } + + mapBuilt = true; + } } namespace sf @@ -64,6 +244,13 @@ Display* OpenDisplay() //////////////////////////////////////////////////////////// +xcb_connection_t* OpenConnection() +{ + return XGetXCBConnection(OpenDisplay()); +} + + +//////////////////////////////////////////////////////////// void CloseDisplay(Display* display) { assert(display == sharedDisplay); @@ -73,6 +260,95 @@ void CloseDisplay(Display* display) XCloseDisplay(display); } + +//////////////////////////////////////////////////////////// +void CloseConnection(xcb_connection_t* connection) +{ + assert(connection == XGetXCBConnection(sharedDisplay)); + return CloseDisplay(sharedDisplay); +} + + +//////////////////////////////////////////////////////////// +xcb_screen_t* XCBScreenOfDisplay(xcb_connection_t* connection, int screen_nbr) +{ + xcb_screen_iterator_t iter = xcb_setup_roots_iterator(xcb_get_setup(connection)); + + for (; iter.rem; --screen_nbr, xcb_screen_next (&iter)) + { + if (screen_nbr == 0) + return iter.data; + } + + return NULL; +} + + +//////////////////////////////////////////////////////////// +xcb_screen_t* XCBDefaultScreen(xcb_connection_t* connection) +{ + assert(connection == XGetXCBConnection(sharedDisplay)); + return XCBScreenOfDisplay(connection, XDefaultScreen(sharedDisplay)); +} + + +//////////////////////////////////////////////////////////// +xcb_window_t XCBDefaultRootWindow(xcb_connection_t* connection) +{ + assert(connection == XGetXCBConnection(sharedDisplay)); + xcb_screen_t* screen = XCBScreenOfDisplay(connection, XDefaultScreen(sharedDisplay)); + if (screen) + return screen->root; + return 0; +} + + +//////////////////////////////////////////////////////////// +xcb_atom_t getAtom(const std::string& name, bool onlyIfExists) +{ + AtomMap::const_iterator iter = atoms.find(name); + + if (iter != atoms.end()) + return iter->second; + + ScopedXcbPtr<xcb_generic_error_t> error(NULL); + + xcb_connection_t* connection = OpenConnection(); + + ScopedXcbPtr<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply( + connection, + xcb_intern_atom( + connection, + onlyIfExists, + name.size(), + name.c_str() + ), + &error + )); + + CloseConnection(connection); + + if (error || !reply) + { + err() << "Failed to get " << name << " atom." << std::endl; + return XCB_ATOM_NONE; + } + + atoms[name] = reply->atom; + + return reply->atom; +} + + +//////////////////////////////////////////////////////////// +const xcb_keysym_t* getKeysymMap() +{ + if (!mapBuilt) + buildMap(); + + return keysymMap; +} + } // namespace priv } // namespace sf diff --git a/src/SFML/Window/Unix/Display.hpp b/src/SFML/Window/Unix/Display.hpp index df018b3..1cda4a7 100644 --- a/src/SFML/Window/Unix/Display.hpp +++ b/src/SFML/Window/Unix/Display.hpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library -// Copyright (C) 2007-2014 Laurent Gomila (laurent.gom@gmail.com) +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the use of this software. @@ -28,7 +28,8 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include <X11/Xlib.h> +#include <X11/Xlib-xcb.h> +#include <string> namespace sf @@ -47,13 +48,85 @@ namespace priv Display* OpenDisplay(); //////////////////////////////////////////////////////////// -/// \brief Release a reference to the shared +/// \brief Get the xcb connection of the shared Display +/// +/// This function increments the reference count of the display, +/// it must be matched with a call to CloseDisplay. +/// +/// \return Pointer to the shared connection +/// +//////////////////////////////////////////////////////////// +xcb_connection_t* OpenConnection(); + +//////////////////////////////////////////////////////////// +/// \brief Release a reference to the shared display /// /// \param display Display to release /// //////////////////////////////////////////////////////////// void CloseDisplay(Display* display); +//////////////////////////////////////////////////////////// +/// \brief Release a reference to the shared display +/// +/// \param connection Connection of display to release +/// +//////////////////////////////////////////////////////////// +void CloseConnection(xcb_connection_t* connection); + +//////////////////////////////////////////////////////////// +/// \brief Get screen of a display by index (equivalent to XScreenOfDisplay) +/// +/// \param connection Connection of display +/// \param screen_nbr The index of the screen +/// +/// \return Pointer to the screen +/// +//////////////////////////////////////////////////////////// +xcb_screen_t* XCBScreenOfDisplay(xcb_connection_t* connection, int screen_nbr); + +//////////////////////////////////////////////////////////// +/// \brief Get default screen of a display (equivalent to XDefaultScreen) +/// +/// \param connection Connection of display +/// +/// \return Pointer to the default screen of the display +/// +//////////////////////////////////////////////////////////// +xcb_screen_t* XCBDefaultScreen(xcb_connection_t* connection); + +//////////////////////////////////////////////////////////// +/// \brief Get default root window of a display (equivalent to XDefaultRootWindow) +/// +/// \param connection Connection of display +/// +/// \return Root window of the display +/// +//////////////////////////////////////////////////////////// +xcb_window_t XCBDefaultRootWindow(xcb_connection_t* connection); + +//////////////////////////////////////////////////////////// +/// \brief Get the atom with the specified name +/// +/// \param name Name of the atom +/// \param onlyIfExists Don't try to create the atom if it doesn't already exist +/// +/// \return Atom if it exists or XCB_ATOM_NONE (0) if it doesn't +/// +//////////////////////////////////////////////////////////// +xcb_atom_t getAtom(const std::string& name, bool onlyIfExists = false); + +//////////////////////////////////////////////////////////// +/// \brief Get the keycode to keysym map +/// +/// Contains 255 values. Use the keycode as the index +/// into the array to retrieve its keysym. +/// +/// \return Keycode to keysym map +/// +//////////////////////////////////////////////////////////// +const xcb_keysym_t* getKeysymMap(); + } // namespace priv } // namespace sf diff --git a/src/SFML/Window/Unix/GlxContext.cpp b/src/SFML/Window/Unix/GlxContext.cpp index eac6099..c89c9d3 100644 --- a/src/SFML/Window/Unix/GlxContext.cpp +++ b/src/SFML/Window/Unix/GlxContext.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library -// Copyright (C) 2007-2014 Laurent Gomila (laurent.gom@gmail.com) +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the use of this software. @@ -28,15 +28,72 @@ #include <SFML/Window/Unix/GlxContext.hpp> #include <SFML/Window/Unix/WindowImplX11.hpp> #include <SFML/Window/Unix/Display.hpp> -#include <SFML/OpenGL.hpp> +#include <SFML/System/Mutex.hpp> +#include <SFML/System/Lock.hpp> #include <SFML/System/Err.hpp> +#if !defined(GLX_DEBUGGING) && defined(SFML_DEBUG) + // Enable this to print messages to err() everytime GLX produces errors + //#define GLX_DEBUGGING +#endif + +namespace +{ + sf::Mutex glxErrorMutex; + bool glxErrorOccurred = false; + + int HandleXError(::Display*, XErrorEvent*) + { + glxErrorOccurred = true; + return 0; + } + + class GlxErrorHandler + { + public: + + GlxErrorHandler(::Display* display) : + m_display(display), + m_lock (glxErrorMutex) + { + glxErrorOccurred = false; + m_previousHandler = XSetErrorHandler(HandleXError); + } + + ~GlxErrorHandler() + { + XSync(m_display, False); + XSetErrorHandler(m_previousHandler); + } + + private: + sf::Lock m_lock; + ::Display* m_display; + int (*m_previousHandler)(::Display*, XErrorEvent*); + }; +} + namespace sf { namespace priv { //////////////////////////////////////////////////////////// +void ensureExtensionsInit(::Display* display, int screen) +{ + static bool initialized = false; + if (!initialized) + { + initialized = true; + + // We don't check the return value since the extension + // flags are cleared even if loading fails + sfglx_LoadFunctions(display, screen); + } +} + + +//////////////////////////////////////////////////////////// GlxContext::GlxContext(GlxContext* shared) : m_window (0), m_context (NULL), @@ -44,18 +101,32 @@ m_ownsWindow(true) { // Open a connection with the X server m_display = OpenDisplay(); + m_connection = XGetXCBConnection(m_display); + xcb_screen_t* screen = XCBScreenOfDisplay(m_connection, DefaultScreen(m_display)); + + // Choose the visual according to the context settings + XVisualInfo visualInfo = selectBestVisual(m_display, VideoMode::getDesktopMode().bitsPerPixel, ContextSettings()); + + // Define the window attributes + xcb_colormap_t colormap = xcb_generate_id(m_connection); + xcb_create_colormap(m_connection, XCB_COLORMAP_ALLOC_NONE, colormap, screen->root, visualInfo.visualid); + const uint32_t value_list[] = {colormap}; // Create a dummy window (disabled and hidden) - int screen = DefaultScreen(m_display); - m_window = XCreateWindow(m_display, - RootWindow(m_display, screen), - 0, 0, - 1, 1, - 0, - DefaultDepth(m_display, screen), - InputOutput, - DefaultVisual(m_display, screen), - 0, NULL); + m_window = xcb_generate_id(m_connection); + xcb_create_window( + m_connection, + static_cast<uint8_t>(visualInfo.depth), + m_window, + screen->root, + 0, 0, + 1, 1, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + visualInfo.visualid, + XCB_CW_COLORMAP, + value_list + ); // Create the context createContext(shared, VideoMode::getDesktopMode().bitsPerPixel, ContextSettings()); @@ -71,6 +142,7 @@ m_ownsWindow(false) // Open a connection with the X server // (important: must be the same display as the owner window) m_display = OpenDisplay(); + m_connection = XGetXCBConnection(m_display); // Get the owner window and its device context m_window = static_cast< ::Window>(owner->getSystemHandle()); @@ -89,18 +161,32 @@ m_ownsWindow(true) { // Open a connection with the X server m_display = OpenDisplay(); + m_connection = XGetXCBConnection(m_display); + xcb_screen_t* screen = XCBScreenOfDisplay(m_connection, DefaultScreen(m_display)); + + // Choose the visual according to the context settings + XVisualInfo visualInfo = selectBestVisual(m_display, VideoMode::getDesktopMode().bitsPerPixel, settings); + + // Define the window attributes + xcb_colormap_t colormap = xcb_generate_id(m_connection); + xcb_create_colormap(m_connection, XCB_COLORMAP_ALLOC_NONE, colormap, screen->root, visualInfo.visualid); + const uint32_t value_list[] = {colormap}; // Create the hidden window - int screen = DefaultScreen(m_display); - m_window = XCreateWindow(m_display, - RootWindow(m_display, screen), - 0, 0, - width, height, - 0, - DefaultDepth(m_display, screen), - InputOutput, - DefaultVisual(m_display, screen), - 0, NULL); + m_window = xcb_generate_id(m_connection); + xcb_create_window( + m_connection, + static_cast<uint8_t>(visualInfo.depth), + m_window, + screen->root, + 0, 0, + width, height, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + visualInfo.visualid, + XCB_CW_COLORMAP, + value_list + ); // Create the context createContext(shared, VideoMode::getDesktopMode().bitsPerPixel, settings); @@ -113,16 +199,25 @@ GlxContext::~GlxContext() // Destroy the context if (m_context) { +#if defined(GLX_DEBUGGING) + GlxErrorHandler handler(m_display); +#endif + if (glXGetCurrentContext() == m_context) glXMakeCurrent(m_display, None, NULL); glXDestroyContext(m_display, m_context); + +#if defined(GLX_DEBUGGING) + if (glxErrorOccurred) + err() << "GLX error in GlxContext::~GlxContext()" << std::endl; +#endif } // Destroy the window if we own it if (m_window && m_ownsWindow) { - XDestroyWindow(m_display, m_window); - XFlush(m_display); + xcb_destroy_window(m_connection, m_window); + xcb_flush(m_connection); } // Close the connection with the X server @@ -131,27 +226,88 @@ GlxContext::~GlxContext() //////////////////////////////////////////////////////////// +GlFunctionPointer GlxContext::getFunction(const char* name) +{ + return reinterpret_cast<GlFunctionPointer>(glXGetProcAddressARB(reinterpret_cast<const GLubyte*>(name))); +} + + +//////////////////////////////////////////////////////////// bool GlxContext::makeCurrent() { - return m_context && glXMakeCurrent(m_display, m_window, m_context); + if (!m_context) + return false; + +#if defined(GLX_DEBUGGING) + GlxErrorHandler handler(m_display); +#endif + + bool result = glXMakeCurrent(m_display, m_window, m_context); + +#if defined(GLX_DEBUGGING) + if (glxErrorOccurred) + err() << "GLX error in GlxContext::makeCurrent()" << std::endl; +#endif + + return result; } //////////////////////////////////////////////////////////// void GlxContext::display() { +#if defined(GLX_DEBUGGING) + GlxErrorHandler handler(m_display); +#endif + if (m_window) glXSwapBuffers(m_display, m_window); + +#if defined(GLX_DEBUGGING) + if (glxErrorOccurred) + err() << "GLX error in GlxContext::display()" << std::endl; +#endif } //////////////////////////////////////////////////////////// void GlxContext::setVerticalSyncEnabled(bool enabled) { - const GLubyte* name = reinterpret_cast<const GLubyte*>("glXSwapIntervalSGI"); - PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI = reinterpret_cast<PFNGLXSWAPINTERVALSGIPROC>(glXGetProcAddress(name)); - if (glXSwapIntervalSGI) - glXSwapIntervalSGI(enabled ? 1 : 0); + // Make sure that extensions are initialized + ensureExtensionsInit(m_display, DefaultScreen(m_display)); + + int result = 0; + + // Prioritize the EXT variant and fall back to MESA or SGI if needed + // We use the direct pointer to the MESA entry point instead of the alias + // because glx.h declares the entry point as an external function + // which would require us to link in an additional library + if (sfglx_ext_EXT_swap_control == sfglx_LOAD_SUCCEEDED) + { + glXSwapIntervalEXT(m_display, glXGetCurrentDrawable(), enabled ? 1 : 0); + } + else if (sfglx_ext_MESA_swap_control == sfglx_LOAD_SUCCEEDED) + { + result = sf_ptrc_glXSwapIntervalMESA(enabled ? 1 : 0); + } + else if (sfglx_ext_SGI_swap_control == sfglx_LOAD_SUCCEEDED) + { + result = glXSwapIntervalSGI(enabled ? 1 : 0); + } + else + { + static bool warned = false; + + if (!warned) + { + err() << "Setting vertical sync not supported" << std::endl; + + warned = true; + } + } + + if (result != 0) + err() << "Setting vertical sync failed" << std::endl; } @@ -164,7 +320,7 @@ XVisualInfo GlxContext::selectBestVisual(::Display* display, unsigned int bitsPe if (visuals) { // Evaluate all the returned visuals, and pick the best one - int bestScore = 0xFFFF; + int bestScore = 0x7FFFFFFF; XVisualInfo bestVisual; for (int i = 0; i < count; ++i) { @@ -176,18 +332,30 @@ XVisualInfo GlxContext::selectBestVisual(::Display* display, unsigned int bitsPe // Extract the components of the current visual int red, green, blue, alpha, depth, stencil, multiSampling, samples; - glXGetConfig(display, &visuals[i], GLX_RED_SIZE, &red); - glXGetConfig(display, &visuals[i], GLX_GREEN_SIZE, &green); - glXGetConfig(display, &visuals[i], GLX_BLUE_SIZE, &blue); - glXGetConfig(display, &visuals[i], GLX_ALPHA_SIZE, &alpha); - glXGetConfig(display, &visuals[i], GLX_DEPTH_SIZE, &depth); - glXGetConfig(display, &visuals[i], GLX_STENCIL_SIZE, &stencil); - glXGetConfig(display, &visuals[i], GLX_SAMPLE_BUFFERS_ARB, &multiSampling); - glXGetConfig(display, &visuals[i], GLX_SAMPLES_ARB, &samples); + glXGetConfig(display, &visuals[i], GLX_RED_SIZE, &red); + glXGetConfig(display, &visuals[i], GLX_GREEN_SIZE, &green); + glXGetConfig(display, &visuals[i], GLX_BLUE_SIZE, &blue); + glXGetConfig(display, &visuals[i], GLX_ALPHA_SIZE, &alpha); + glXGetConfig(display, &visuals[i], GLX_DEPTH_SIZE, &depth); + glXGetConfig(display, &visuals[i], GLX_STENCIL_SIZE, &stencil); + + if (sfglx_ext_ARB_multisample == sfglx_LOAD_SUCCEEDED) + { + glXGetConfig(display, &visuals[i], GLX_SAMPLE_BUFFERS_ARB, &multiSampling); + glXGetConfig(display, &visuals[i], GLX_SAMPLES_ARB, &samples); + } + else + { + multiSampling = 0; + samples = 0; + } + + // TODO: Replace this with proper acceleration detection + bool accelerated = true; // Evaluate the visual int color = red + green + blue + alpha; - int score = evaluateFormat(bitsPerPixel, settings, color, depth, stencil, multiSampling ? samples : 0); + int score = evaluateFormat(bitsPerPixel, settings, color, depth, stencil, multiSampling ? samples : 0, accelerated); // If it's better than the current best, make it the new best if (score < bestScore) @@ -211,126 +379,207 @@ XVisualInfo GlxContext::selectBestVisual(::Display* display, unsigned int bitsPe } } - //////////////////////////////////////////////////////////// void GlxContext::createContext(GlxContext* shared, unsigned int bitsPerPixel, const ContextSettings& settings) { - XVisualInfo* visualInfo = NULL; - // Save the creation settings m_settings = settings; + // Retrieve the attributes of the target window + XWindowAttributes windowAttributes; + if (XGetWindowAttributes(m_display, m_window, &windowAttributes) == 0) + { + err() << "Failed to get the window attributes" << std::endl; + return; + } + + // Get its visuals + XVisualInfo tpl; + tpl.screen = DefaultScreen(m_display); + tpl.visualid = XVisualIDFromVisual(windowAttributes.visual); + int nbVisuals = 0; + XVisualInfo* visualInfo = XGetVisualInfo(m_display, VisualIDMask | VisualScreenMask, &tpl, &nbVisuals); + // Get the context to share display lists with GLXContext toShare = shared ? shared->m_context : NULL; - // Create the OpenGL context -- first try context versions >= 3.0 if it is requested (they require special code) - if (m_settings.majorVersion >= 3) + // There are no GLX versions prior to 1.0 + int major = 0; + int minor = 0; + + if (!glXQueryVersion(m_display, &major, &minor)) + err() << "Failed to query GLX version, limited to legacy context creation" << std::endl; + + // Make sure that extensions are initialized if this is not the shared context + // The shared context is the context used to initialize the extensions + if (shared) + ensureExtensionsInit(m_display, DefaultScreen(m_display)); + + // Check if glXCreateContextAttribsARB is available (requires GLX 1.3 or greater) + bool hasCreateContextArb = (sfglx_ext_ARB_create_context == sfglx_LOAD_SUCCEEDED) && ((major > 1) || (minor >= 3)); + + // Check if we need to use glXCreateContextAttribsARB + bool needCreateContextArb = false; + + if (m_settings.attributeFlags) + needCreateContextArb = true; + else if (m_settings.majorVersion >= 3) + needCreateContextArb = true; + + // Create the OpenGL context -- first try using glXCreateContextAttribsARB if we need to + if (hasCreateContextArb && needCreateContextArb) { - const GLubyte* name = reinterpret_cast<const GLubyte*>("glXCreateContextAttribsARB"); - PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = reinterpret_cast<PFNGLXCREATECONTEXTATTRIBSARBPROC>(glXGetProcAddress(name)); - if (glXCreateContextAttribsARB) + // Get a GLXFBConfig that matches the the window's visual, for glXCreateContextAttribsARB + GLXFBConfig* config = NULL; + + // We don't supply attributes to match against, since + // the visual we are matching against was already + // deemed suitable in selectBestVisual() + int nbConfigs = 0; + GLXFBConfig* configs = glXChooseFBConfig(m_display, DefaultScreen(m_display), NULL, &nbConfigs); + + for (int i = 0; configs && (i < nbConfigs); ++i) { - // Select a GLXFB config that matches the requested context settings - int nbConfigs = 0; - int fbAttributes[] = + XVisualInfo* visual = glXGetVisualFromFBConfig(m_display, configs[i]); + + if (!visual) + continue; + + if (visual->visualid == visualInfo->visualid) + { + config = &configs[i]; + break; + } + } + + if (!config) + err() << "Failed to get GLXFBConfig which corresponds to the window's visual" << std::endl; + + while (config && !m_context && m_settings.majorVersion) + { + // Check if setting the profile is supported + if (sfglx_ext_ARB_create_context_profile == sfglx_LOAD_SUCCEEDED) + { + int profile = (m_settings.attributeFlags & ContextSettings::Core) ? GLX_CONTEXT_CORE_PROFILE_BIT_ARB : GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; + int debug = (m_settings.attributeFlags & ContextSettings::Debug) ? GLX_CONTEXT_DEBUG_BIT_ARB : 0; + + // Create the context + int attributes[] = + { + GLX_CONTEXT_MAJOR_VERSION_ARB, static_cast<int>(m_settings.majorVersion), + GLX_CONTEXT_MINOR_VERSION_ARB, static_cast<int>(m_settings.minorVersion), + GLX_CONTEXT_PROFILE_MASK_ARB, profile, + GLX_CONTEXT_FLAGS_ARB, debug, + 0, 0 + }; + + // RAII GLX error handler (we simply ignore errors here) + // On an error, glXCreateContextAttribsARB will return 0 anyway + GlxErrorHandler handler(m_display); + + m_context = glXCreateContextAttribsARB(m_display, *config, toShare, true, attributes); + } + else { - GLX_DEPTH_SIZE, static_cast<int>(settings.depthBits), - GLX_STENCIL_SIZE, static_cast<int>(settings.stencilBits), - GLX_SAMPLE_BUFFERS, settings.antialiasingLevel > 0, - GLX_SAMPLES, static_cast<int>(settings.antialiasingLevel), - GLX_RED_SIZE, 8, - GLX_GREEN_SIZE, 8, - GLX_BLUE_SIZE, 8, - GLX_ALPHA_SIZE, bitsPerPixel == 32 ? 8 : 0, - GLX_DOUBLEBUFFER, True, - GLX_X_RENDERABLE, True, - GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, - GLX_RENDER_TYPE, GLX_RGBA_BIT, - GLX_CONFIG_CAVEAT, GLX_NONE, - None - }; - GLXFBConfig* configs = glXChooseFBConfig(m_display, DefaultScreen(m_display), fbAttributes, &nbConfigs); - if (configs && nbConfigs) + if ((m_settings.attributeFlags & ContextSettings::Core) || (m_settings.attributeFlags & ContextSettings::Debug)) + err() << "Selecting a profile during context creation is not supported," + << "disabling comptibility and debug" << std::endl; + + m_settings.attributeFlags = ContextSettings::Default; + + // Create the context + int attributes[] = + { + GLX_CONTEXT_MAJOR_VERSION_ARB, static_cast<int>(m_settings.majorVersion), + GLX_CONTEXT_MINOR_VERSION_ARB, static_cast<int>(m_settings.minorVersion), + 0, 0 + }; + + // RAII GLX error handler (we simply ignore errors here) + // On an error, glXCreateContextAttribsARB will return 0 anyway + GlxErrorHandler handler(m_display); + + m_context = glXCreateContextAttribsARB(m_display, *config, toShare, true, attributes); + } + + if (!m_context) { - while (!m_context && (m_settings.majorVersion >= 3)) + // If we couldn't create the context, first try disabling flags, + // then lower the version number and try again -- stop at 0.0 + // Invalid version numbers will be generated by this algorithm (like 3.9), but we really don't care + if (m_settings.attributeFlags != ContextSettings::Default) { - // Create the context - int attributes[] = - { - GLX_CONTEXT_MAJOR_VERSION_ARB, static_cast<int>(m_settings.majorVersion), - GLX_CONTEXT_MINOR_VERSION_ARB, static_cast<int>(m_settings.minorVersion), - GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, - 0, 0 - }; - m_context = glXCreateContextAttribsARB(m_display, configs[0], toShare, true, attributes); - - if (m_context) - { - // Ok: retrieve the config's visual - visualInfo = glXGetVisualFromFBConfig(m_display, configs[0]); - } - else - { - // If we couldn't create the context, lower the version number and try again -- stop at 3.0 - // Invalid version numbers will be generated by this algorithm (like 3.9), but we really don't care - if (m_settings.minorVersion > 0) - { - // If the minor version is not 0, we decrease it and try again - m_settings.minorVersion--; - } - else - { - // If the minor version is 0, we decrease the major version - m_settings.majorVersion--; - m_settings.minorVersion = 9; - } - } + m_settings.attributeFlags = ContextSettings::Default; + } + else if (m_settings.minorVersion > 0) + { + // If the minor version is not 0, we decrease it and try again + m_settings.minorVersion--; + + m_settings.attributeFlags = settings.attributeFlags; + } + else + { + // If the minor version is 0, we decrease the major version + m_settings.majorVersion--; + m_settings.minorVersion = 9; + + m_settings.attributeFlags = settings.attributeFlags; } - XFree(configs); } } + + if (configs) + XFree(configs); } - // If the OpenGL >= 3.0 context failed or if we don't want one, create a regular OpenGL 1.x/2.x context + // If glXCreateContextAttribsARB failed, use glXCreateContext if (!m_context) { - // set the context version to 2.0 (arbitrary) + // set the context version to 2.1 (arbitrary) and disable flags m_settings.majorVersion = 2; - m_settings.minorVersion = 0; - - // Retrieve the attributes of the target window - XWindowAttributes windowAttributes; - if (XGetWindowAttributes(m_display, m_window, &windowAttributes) == 0) - { - err() << "Failed to get the window attributes" << std::endl; - return; - } + m_settings.minorVersion = 1; + m_settings.attributeFlags = ContextSettings::Default; - // Get its visual - XVisualInfo tpl; - tpl.screen = DefaultScreen(m_display); - tpl.visualid = XVisualIDFromVisual(windowAttributes.visual); - int nbVisuals = 0; - visualInfo = XGetVisualInfo(m_display, VisualIDMask | VisualScreenMask, &tpl, &nbVisuals); +#if defined(GLX_DEBUGGING) + GlxErrorHandler handler(m_display); +#endif // Create the context, using the target window's visual m_context = glXCreateContext(m_display, visualInfo, toShare, true); - if (!m_context) + +#if defined(GLX_DEBUGGING) + if (glxErrorOccurred) + err() << "GLX error in GlxContext::createContext()" << std::endl; +#endif + } + + if (!m_context) + { + err() << "Failed to create an OpenGL context for this window" << std::endl; + } + else + { + // Update the creation settings from the chosen format + int depth, stencil, multiSampling, samples; + glXGetConfig(m_display, visualInfo, GLX_DEPTH_SIZE, &depth); + glXGetConfig(m_display, visualInfo, GLX_STENCIL_SIZE, &stencil); + + if (sfglx_ext_ARB_multisample == sfglx_LOAD_SUCCEEDED) { - err() << "Failed to create an OpenGL context for this window" << std::endl; - return; + glXGetConfig(m_display, visualInfo, GLX_SAMPLE_BUFFERS_ARB, &multiSampling); + glXGetConfig(m_display, visualInfo, GLX_SAMPLES_ARB, &samples); + } + else + { + multiSampling = 0; + samples = 0; } - } - // Update the creation settings from the chosen format - int depth, stencil, multiSampling, samples; - glXGetConfig(m_display, visualInfo, GLX_DEPTH_SIZE, &depth); - glXGetConfig(m_display, visualInfo, GLX_STENCIL_SIZE, &stencil); - glXGetConfig(m_display, visualInfo, GLX_SAMPLE_BUFFERS_ARB, &multiSampling); - glXGetConfig(m_display, visualInfo, GLX_SAMPLES_ARB, &samples); - m_settings.depthBits = static_cast<unsigned int>(depth); - m_settings.stencilBits = static_cast<unsigned int>(stencil); - m_settings.antialiasingLevel = multiSampling ? samples : 0; + m_settings.depthBits = static_cast<unsigned int>(depth); + m_settings.stencilBits = static_cast<unsigned int>(stencil); + m_settings.antialiasingLevel = multiSampling ? samples : 0; + } // Free the visual info XFree(visualInfo); diff --git a/src/SFML/Window/Unix/GlxContext.hpp b/src/SFML/Window/Unix/GlxContext.hpp index 0b5982f..e1ad899 100644 --- a/src/SFML/Window/Unix/GlxContext.hpp +++ b/src/SFML/Window/Unix/GlxContext.hpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library -// Copyright (C) 2007-2014 Laurent Gomila (laurent.gom@gmail.com) +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the use of this software. @@ -29,8 +29,8 @@ // Headers //////////////////////////////////////////////////////////// #include <SFML/Window/GlContext.hpp> -#include <X11/Xlib.h> -#include <GL/glx.h> +#include <SFML/Window/Unix/GlxExtensions.hpp> +#include <X11/Xlib-xcb.h> namespace sf @@ -82,6 +82,16 @@ public: ~GlxContext(); //////////////////////////////////////////////////////////// + /// \brief Get the address of an OpenGL function + /// + /// \param name Name of the function to get the address of + /// + /// \return Address of the OpenGL function, 0 on failure + /// + //////////////////////////////////////////////////////////// + static GlFunctionPointer getFunction(const char* name); + + //////////////////////////////////////////////////////////// /// \brief Activate the context as the current target for rendering /// /// \return True on success, false if any error happened @@ -135,10 +145,11 @@ private: //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// - ::Display* m_display; ///< Connection to the X server - ::Window m_window; ///< Window to which the context is attached - GLXContext m_context; ///< OpenGL context - bool m_ownsWindow; ///< Do we own the window associated to the context? + ::Display* m_display; ///< Connection to the X server + ::Window m_window; ///< Window to which the context is attached + xcb_connection_t* m_connection; ///< Pointer to the xcb connection + GLXContext m_context; ///< OpenGL context + bool m_ownsWindow; ///< Do we own the window associated to the context? }; } // namespace priv diff --git a/src/SFML/Window/Unix/GlxExtensions.cpp b/src/SFML/Window/Unix/GlxExtensions.cpp new file mode 100644 index 0000000..029095c --- /dev/null +++ b/src/SFML/Window/Unix/GlxExtensions.cpp @@ -0,0 +1,197 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include <SFML/Window/Unix/GlxExtensions.hpp> +#include <SFML/Window/Context.hpp> +#include <cstdlib> +#include <cstring> +#include <cstddef> + +static sf::GlFunctionPointer IntGetProcAddress(const char* name) +{ + return sf::Context::getFunction(name); +} + +int sfglx_ext_EXT_swap_control = sfglx_LOAD_FAILED; +int sfglx_ext_MESA_swap_control = sfglx_LOAD_FAILED; +int sfglx_ext_SGI_swap_control = sfglx_LOAD_FAILED; +int sfglx_ext_ARB_multisample = sfglx_LOAD_FAILED; +int sfglx_ext_ARB_create_context = sfglx_LOAD_FAILED; +int sfglx_ext_ARB_create_context_profile = sfglx_LOAD_FAILED; + +void (CODEGEN_FUNCPTR *sf_ptrc_glXSwapIntervalEXT)(Display *, GLXDrawable, int) = NULL; + +static int Load_EXT_swap_control(void) +{ + int numFailed = 0; + sf_ptrc_glXSwapIntervalEXT = (void (CODEGEN_FUNCPTR *)(Display *, GLXDrawable, int))IntGetProcAddress("glXSwapIntervalEXT"); + if(!sf_ptrc_glXSwapIntervalEXT) numFailed++; + return numFailed; +} + +int (CODEGEN_FUNCPTR *sf_ptrc_glXSwapIntervalMESA)(int) = NULL; + +static int Load_MESA_swap_control(void) +{ + int numFailed = 0; + sf_ptrc_glXSwapIntervalMESA = (int (CODEGEN_FUNCPTR *)(int))IntGetProcAddress("glXSwapIntervalMESA"); + if(!sf_ptrc_glXSwapIntervalMESA) numFailed++; + return numFailed; +} + +int (CODEGEN_FUNCPTR *sf_ptrc_glXSwapIntervalSGI)(int) = NULL; + +static int Load_SGI_swap_control(void) +{ + int numFailed = 0; + sf_ptrc_glXSwapIntervalSGI = (int (CODEGEN_FUNCPTR *)(int))IntGetProcAddress("glXSwapIntervalSGI"); + if(!sf_ptrc_glXSwapIntervalSGI) numFailed++; + return numFailed; +} + +GLXContext (CODEGEN_FUNCPTR *sf_ptrc_glXCreateContextAttribsARB)(Display *, GLXFBConfig, GLXContext, Bool, const int *) = NULL; + +static int Load_ARB_create_context(void) +{ + int numFailed = 0; + sf_ptrc_glXCreateContextAttribsARB = (GLXContext (CODEGEN_FUNCPTR *)(Display *, GLXFBConfig, GLXContext, Bool, const int *))IntGetProcAddress("glXCreateContextAttribsARB"); + if(!sf_ptrc_glXCreateContextAttribsARB) numFailed++; + return numFailed; +} + +typedef int (*PFN_LOADFUNCPOINTERS)(void); +typedef struct sfglx_StrToExtMap_s +{ + const char *extensionName; + int *extensionVariable; + PFN_LOADFUNCPOINTERS LoadExtension; +} sfglx_StrToExtMap; + +static sfglx_StrToExtMap ExtensionMap[6] = { + {"GLX_EXT_swap_control", &sfglx_ext_EXT_swap_control, Load_EXT_swap_control}, + {"GLX_MESA_swap_control", &sfglx_ext_MESA_swap_control, Load_MESA_swap_control}, + {"GLX_SGI_swap_control", &sfglx_ext_SGI_swap_control, Load_SGI_swap_control}, + {"GLX_ARB_multisample", &sfglx_ext_ARB_multisample, NULL}, + {"GLX_ARB_create_context", &sfglx_ext_ARB_create_context, Load_ARB_create_context}, + {"GLX_ARB_create_context_profile", &sfglx_ext_ARB_create_context_profile, NULL}, +}; + +static int g_extensionMapSize = 6; + +static sfglx_StrToExtMap *FindExtEntry(const char *extensionName) +{ + int loop; + sfglx_StrToExtMap *currLoc = ExtensionMap; + for(loop = 0; loop < g_extensionMapSize; ++loop, ++currLoc) + { + if(strcmp(extensionName, currLoc->extensionName) == 0) + return currLoc; + } + + return NULL; +} + +static void ClearExtensionVars(void) +{ + sfglx_ext_EXT_swap_control = sfglx_LOAD_FAILED; + sfglx_ext_MESA_swap_control = sfglx_LOAD_FAILED; + sfglx_ext_SGI_swap_control = sfglx_LOAD_FAILED; + sfglx_ext_ARB_multisample = sfglx_LOAD_FAILED; + sfglx_ext_ARB_create_context = sfglx_LOAD_FAILED; + sfglx_ext_ARB_create_context_profile = sfglx_LOAD_FAILED; +} + + +static void LoadExtByName(const char *extensionName) +{ + sfglx_StrToExtMap *entry = NULL; + entry = FindExtEntry(extensionName); + if(entry) + { + if(entry->LoadExtension) + { + int numFailed = entry->LoadExtension(); + if(numFailed == 0) + { + *(entry->extensionVariable) = sfglx_LOAD_SUCCEEDED; + } + else + { + *(entry->extensionVariable) = sfglx_LOAD_SUCCEEDED + numFailed; + } + } + else + { + *(entry->extensionVariable) = sfglx_LOAD_SUCCEEDED; + } + } +} + + +static void ProcExtsFromExtString(const char *strExtList) +{ + size_t iExtListLen = strlen(strExtList); + const char *strExtListEnd = strExtList + iExtListLen; + const char *strCurrPos = strExtList; + char strWorkBuff[256]; + + while(*strCurrPos) + { + /*Get the extension at our position.*/ + int iStrLen = 0; + const char *strEndStr = strchr(strCurrPos, ' '); + int iStop = 0; + if(strEndStr == NULL) + { + strEndStr = strExtListEnd; + iStop = 1; + } + + iStrLen = (int)((ptrdiff_t)strEndStr - (ptrdiff_t)strCurrPos); + + if(iStrLen > 255) + return; + + strncpy(strWorkBuff, strCurrPos, iStrLen); + strWorkBuff[iStrLen] = '\0'; + + LoadExtByName(strWorkBuff); + + strCurrPos = strEndStr + 1; + if(iStop) break; + } +} + +int sfglx_LoadFunctions(Display *display, int screen) +{ + ClearExtensionVars(); + + + ProcExtsFromExtString((const char *)glXQueryExtensionsString(display, screen)); + return sfglx_LOAD_SUCCEEDED; +} + diff --git a/src/SFML/Window/Unix/GlxExtensions.hpp b/src/SFML/Window/Unix/GlxExtensions.hpp new file mode 100644 index 0000000..9e9a749 --- /dev/null +++ b/src/SFML/Window/Unix/GlxExtensions.hpp @@ -0,0 +1,203 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#ifndef SF_POINTER_C_GENERATED_HEADER_GLXWIN_HPP +#define SF_POINTER_C_GENERATED_HEADER_GLXWIN_HPP + +#ifdef __glxext_h_ +#error Attempt to include glx_exts after including glxext.h +#endif + +#define __glxext_h_ + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <GL/glx.h> +#ifdef CODEGEN_FUNCPTR +#undef CODEGEN_FUNCPTR +#endif /*CODEGEN_FUNCPTR*/ +#define CODEGEN_FUNCPTR + +#ifndef GL_LOAD_GEN_BASIC_OPENGL_TYPEDEFS +#define GL_LOAD_GEN_BASIC_OPENGL_TYPEDEFS + +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef signed char GLbyte; +typedef short GLshort; +typedef int GLint; +typedef int GLsizei; +typedef unsigned char GLubyte; +typedef unsigned short GLushort; +typedef unsigned int GLuint; +typedef float GLfloat; +typedef float GLclampf; +typedef double GLdouble; +typedef double GLclampd; +#define GLvoid void + +#endif /*GL_LOAD_GEN_BASIC_OPENGL_TYPEDEFS*/ + + +#ifndef GL_LOAD_GEN_BASIC_OPENGL_TYPEDEFS +#define GL_LOAD_GEN_BASIC_OPENGL_TYPEDEFS + + +#endif /*GL_LOAD_GEN_BASIC_OPENGL_TYPEDEFS*/ + + +#ifndef GLEXT_64_TYPES_DEFINED +/* This code block is duplicated in glext.h, so must be protected */ +#define GLEXT_64_TYPES_DEFINED +/* Define int32_t, int64_t, and uint64_t types for UST/MSC */ +/* (as used in the GLX_OML_sync_control extension). */ +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#include <inttypes.h> +#elif defined(__sun__) || defined(__digital__) +#include <inttypes.h> +#if defined(__STDC__) +#if defined(__arch64__) || defined(_LP64) +typedef long int int64_t; +typedef unsigned long int uint64_t; +#else +typedef long long int int64_t; +typedef unsigned long long int uint64_t; +#endif /* __arch64__ */ +#endif /* __STDC__ */ +#elif defined( __VMS ) || defined(__sgi) +#include <inttypes.h> +#elif defined(__SCO__) || defined(__USLC__) +#include <stdint.h> +#elif defined(__UNIXOS2__) || defined(__SOL64__) +typedef long int int32_t; +typedef long long int int64_t; +typedef unsigned long long int uint64_t; +#elif defined(_WIN32) && defined(__GNUC__) +#include <stdint.h> +#elif defined(_WIN32) +typedef __int32 int32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +/* Fallback if nothing above works */ +#include <inttypes.h> +#endif +#endif + typedef struct __GLXFBConfigRec *GLXFBConfig; + typedef XID GLXContextID; + typedef struct __GLXcontextRec *GLXContext; + typedef XID GLXPixmap; + typedef XID GLXDrawable; + typedef XID GLXPbuffer; + typedef void (APIENTRY *__GLXextFuncPtr)(void); + typedef XID GLXVideoCaptureDeviceNV; + typedef unsigned int GLXVideoDeviceNV; + typedef XID GLXVideoSourceSGIX; + typedef struct __GLXFBConfigRec *GLXFBConfigSGIX; + typedef XID GLXPbufferSGIX; + typedef struct { + char pipeName[80]; /* Should be [GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX] */ + int networkId; +} GLXHyperpipeNetworkSGIX; + typedef struct { + char pipeName[80]; /* Should be [GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX] */ + int channel; + unsigned int participationType; + int timeSlice; +} GLXHyperpipeConfigSGIX; + typedef struct { + char pipeName[80]; /* Should be [GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX] */ + int srcXOrigin, srcYOrigin, srcWidth, srcHeight; + int destXOrigin, destYOrigin, destWidth, destHeight; +} GLXPipeRect; + typedef struct { + char pipeName[80]; /* Should be [GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX] */ + int XOrigin, YOrigin, maxHeight, maxWidth; +} GLXPipeRectLimits; + +#ifdef __cplusplus +extern "C" { +#endif /*__cplusplus*/ + +extern int sfglx_ext_EXT_swap_control; +extern int sfglx_ext_MESA_swap_control; +extern int sfglx_ext_SGI_swap_control; +extern int sfglx_ext_ARB_multisample; +extern int sfglx_ext_ARB_create_context; +extern int sfglx_ext_ARB_create_context_profile; + +#define GLX_MAX_SWAP_INTERVAL_EXT 0x20F2 +#define GLX_SWAP_INTERVAL_EXT 0x20F1 + +#define GLX_SAMPLES_ARB 100001 +#define GLX_SAMPLE_BUFFERS_ARB 100000 + +#define GLX_CONTEXT_DEBUG_BIT_ARB 0x00000001 +#define GLX_CONTEXT_FLAGS_ARB 0x2094 +#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 + +#define GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 + +#ifndef GLX_EXT_swap_control +#define GLX_EXT_swap_control 1 +extern void (CODEGEN_FUNCPTR *sf_ptrc_glXSwapIntervalEXT)(Display *, GLXDrawable, int); +#define glXSwapIntervalEXT sf_ptrc_glXSwapIntervalEXT +#endif /*GLX_EXT_swap_control*/ + +// Declare entry point even if GLX header already provides glXSwapIntervalMESA +// We won't make use of an alias here +extern int (CODEGEN_FUNCPTR *sf_ptrc_glXSwapIntervalMESA)(int); + +#ifndef GLX_SGI_swap_control +#define GLX_SGI_swap_control 1 +extern int (CODEGEN_FUNCPTR *sf_ptrc_glXSwapIntervalSGI)(int); +#define glXSwapIntervalSGI sf_ptrc_glXSwapIntervalSGI +#endif /*GLX_SGI_swap_control*/ + +#ifndef GLX_ARB_create_context +#define GLX_ARB_create_context 1 +extern GLXContext (CODEGEN_FUNCPTR *sf_ptrc_glXCreateContextAttribsARB)(Display *, GLXFBConfig, GLXContext, Bool, const int *); +#define glXCreateContextAttribsARB sf_ptrc_glXCreateContextAttribsARB +#endif /*GLX_ARB_create_context*/ + + +enum sfglx_LoadStatus +{ + sfglx_LOAD_FAILED = 0, + sfglx_LOAD_SUCCEEDED = 1 +}; + +int sfglx_LoadFunctions(Display *display, int screen); + + +#ifdef __cplusplus +} +#endif /*__cplusplus*/ + +#endif /* SF_POINTER_C_GENERATED_HEADER_GLXWIN_HPP */ diff --git a/src/SFML/Window/Unix/GlxExtensions.txt b/src/SFML/Window/Unix/GlxExtensions.txt new file mode 100644 index 0000000..b6b06fa --- /dev/null +++ b/src/SFML/Window/Unix/GlxExtensions.txt @@ -0,0 +1,11 @@ +// Created with: +// https://bitbucket.org/Anteru/glloadgen-reloaded +// Commit 20f19482b7a844d20b9785c3e3fd1f16419f6e0a +// lua LoadGen.lua -style=pointer_c -spec=glX -indent=space -prefix=sf -extfile=GlxExtensions.txt GlxExtensions + +EXT_swap_control +// MESA_swap_control +SGI_swap_control +GLX_ARB_multisample +GLX_ARB_create_context +GLX_ARB_create_context_profile
\ No newline at end of file diff --git a/src/SFML/Window/Unix/InputImpl.cpp b/src/SFML/Window/Unix/InputImpl.cpp index 8ff7697..4d815f8 100644 --- a/src/SFML/Window/Unix/InputImpl.cpp +++ b/src/SFML/Window/Unix/InputImpl.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library -// Copyright (C) 2007-2014 Laurent Gomila (laurent.gom@gmail.com) +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the use of this software. @@ -25,11 +25,148 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// +#include <SFML/Window/Window.hpp> // important to be included first (conflict with None) #include <SFML/Window/Unix/InputImpl.hpp> -#include <SFML/Window/Window.hpp> #include <SFML/Window/Unix/Display.hpp> -#include <X11/Xlib.h> +#include <SFML/Window/Unix/ScopedXcbPtr.hpp> +#include <SFML/System/Err.hpp> +#include <xcb/xcb.h> #include <X11/keysym.h> +#include <cstdlib> + +//////////////////////////////////////////////////////////// +// Private data +//////////////////////////////////////////////////////////// +namespace +{ + bool mapBuilt = false; + + // We use a simple array instead of a map => constant time lookup + xcb_keycode_t keycodeMap[sf::Keyboard::KeyCount]; + + xcb_keycode_t getKeycode(xcb_keysym_t keysym) + { + const xcb_keysym_t* keysymMap = sf::priv::getKeysymMap(); + + for (xcb_keycode_t i = 0; ; ++i) + { + if (keysymMap[i] == keysym) + return i; + + if (i == 255) + break; + } + + return 255; + } + + void buildMap() + { + keycodeMap[sf::Keyboard::A] = getKeycode(XK_a); + keycodeMap[sf::Keyboard::B] = getKeycode(XK_b); + keycodeMap[sf::Keyboard::C] = getKeycode(XK_c); + keycodeMap[sf::Keyboard::D] = getKeycode(XK_d); + keycodeMap[sf::Keyboard::E] = getKeycode(XK_e); + keycodeMap[sf::Keyboard::F] = getKeycode(XK_f); + keycodeMap[sf::Keyboard::G] = getKeycode(XK_g); + keycodeMap[sf::Keyboard::H] = getKeycode(XK_h); + keycodeMap[sf::Keyboard::I] = getKeycode(XK_i); + keycodeMap[sf::Keyboard::J] = getKeycode(XK_j); + keycodeMap[sf::Keyboard::K] = getKeycode(XK_k); + keycodeMap[sf::Keyboard::L] = getKeycode(XK_l); + keycodeMap[sf::Keyboard::M] = getKeycode(XK_m); + keycodeMap[sf::Keyboard::N] = getKeycode(XK_n); + keycodeMap[sf::Keyboard::O] = getKeycode(XK_o); + keycodeMap[sf::Keyboard::P] = getKeycode(XK_p); + keycodeMap[sf::Keyboard::Q] = getKeycode(XK_q); + keycodeMap[sf::Keyboard::R] = getKeycode(XK_r); + keycodeMap[sf::Keyboard::S] = getKeycode(XK_s); + keycodeMap[sf::Keyboard::T] = getKeycode(XK_t); + keycodeMap[sf::Keyboard::U] = getKeycode(XK_u); + keycodeMap[sf::Keyboard::V] = getKeycode(XK_v); + keycodeMap[sf::Keyboard::W] = getKeycode(XK_w); + keycodeMap[sf::Keyboard::X] = getKeycode(XK_x); + keycodeMap[sf::Keyboard::Y] = getKeycode(XK_y); + keycodeMap[sf::Keyboard::Z] = getKeycode(XK_z); + keycodeMap[sf::Keyboard::Num0] = getKeycode(XK_0); + keycodeMap[sf::Keyboard::Num1] = getKeycode(XK_1); + keycodeMap[sf::Keyboard::Num2] = getKeycode(XK_2); + keycodeMap[sf::Keyboard::Num3] = getKeycode(XK_3); + keycodeMap[sf::Keyboard::Num4] = getKeycode(XK_4); + keycodeMap[sf::Keyboard::Num5] = getKeycode(XK_5); + keycodeMap[sf::Keyboard::Num6] = getKeycode(XK_6); + keycodeMap[sf::Keyboard::Num7] = getKeycode(XK_7); + keycodeMap[sf::Keyboard::Num8] = getKeycode(XK_8); + keycodeMap[sf::Keyboard::Num9] = getKeycode(XK_9); + keycodeMap[sf::Keyboard::Escape] = getKeycode(XK_Escape); + keycodeMap[sf::Keyboard::LControl] = getKeycode(XK_Control_L); + keycodeMap[sf::Keyboard::LShift] = getKeycode(XK_Shift_L); + keycodeMap[sf::Keyboard::LAlt] = getKeycode(XK_Alt_L); + keycodeMap[sf::Keyboard::LSystem] = getKeycode(XK_Super_L); + keycodeMap[sf::Keyboard::RControl] = getKeycode(XK_Control_R); + keycodeMap[sf::Keyboard::RShift] = getKeycode(XK_Shift_R); + keycodeMap[sf::Keyboard::RAlt] = getKeycode(XK_Alt_R); + keycodeMap[sf::Keyboard::RSystem] = getKeycode(XK_Super_R); + keycodeMap[sf::Keyboard::Menu] = getKeycode(XK_Menu); + keycodeMap[sf::Keyboard::LBracket] = getKeycode(XK_bracketleft); + keycodeMap[sf::Keyboard::RBracket] = getKeycode(XK_bracketright); + keycodeMap[sf::Keyboard::SemiColon] = getKeycode(XK_semicolon); + keycodeMap[sf::Keyboard::Comma] = getKeycode(XK_comma); + keycodeMap[sf::Keyboard::Period] = getKeycode(XK_period); + keycodeMap[sf::Keyboard::Quote] = getKeycode(XK_apostrophe); + keycodeMap[sf::Keyboard::Slash] = getKeycode(XK_slash); + keycodeMap[sf::Keyboard::BackSlash] = getKeycode(XK_backslash); + keycodeMap[sf::Keyboard::Tilde] = getKeycode(XK_grave); + keycodeMap[sf::Keyboard::Equal] = getKeycode(XK_equal); + keycodeMap[sf::Keyboard::Dash] = getKeycode(XK_minus); + keycodeMap[sf::Keyboard::Space] = getKeycode(XK_space); + keycodeMap[sf::Keyboard::Return] = getKeycode(XK_Return); + keycodeMap[sf::Keyboard::BackSpace] = getKeycode(XK_BackSpace); + keycodeMap[sf::Keyboard::Tab] = getKeycode(XK_Tab); + keycodeMap[sf::Keyboard::PageUp] = getKeycode(XK_Prior); + keycodeMap[sf::Keyboard::PageDown] = getKeycode(XK_Next); + keycodeMap[sf::Keyboard::End] = getKeycode(XK_End); + keycodeMap[sf::Keyboard::Home] = getKeycode(XK_Home); + keycodeMap[sf::Keyboard::Insert] = getKeycode(XK_Insert); + keycodeMap[sf::Keyboard::Delete] = getKeycode(XK_Delete); + keycodeMap[sf::Keyboard::Add] = getKeycode(XK_KP_Add); + keycodeMap[sf::Keyboard::Subtract] = getKeycode(XK_KP_Subtract); + keycodeMap[sf::Keyboard::Multiply] = getKeycode(XK_KP_Multiply); + keycodeMap[sf::Keyboard::Divide] = getKeycode(XK_KP_Divide); + keycodeMap[sf::Keyboard::Left] = getKeycode(XK_Left); + keycodeMap[sf::Keyboard::Right] = getKeycode(XK_Right); + keycodeMap[sf::Keyboard::Up] = getKeycode(XK_Up); + keycodeMap[sf::Keyboard::Down] = getKeycode(XK_Down); + keycodeMap[sf::Keyboard::Numpad0] = getKeycode(XK_KP_0); + keycodeMap[sf::Keyboard::Numpad1] = getKeycode(XK_KP_1); + keycodeMap[sf::Keyboard::Numpad2] = getKeycode(XK_KP_2); + keycodeMap[sf::Keyboard::Numpad3] = getKeycode(XK_KP_3); + keycodeMap[sf::Keyboard::Numpad4] = getKeycode(XK_KP_4); + keycodeMap[sf::Keyboard::Numpad5] = getKeycode(XK_KP_5); + keycodeMap[sf::Keyboard::Numpad6] = getKeycode(XK_KP_6); + keycodeMap[sf::Keyboard::Numpad7] = getKeycode(XK_KP_7); + keycodeMap[sf::Keyboard::Numpad8] = getKeycode(XK_KP_8); + keycodeMap[sf::Keyboard::Numpad9] = getKeycode(XK_KP_9); + keycodeMap[sf::Keyboard::F1] = getKeycode(XK_F1); + keycodeMap[sf::Keyboard::F2] = getKeycode(XK_F2); + keycodeMap[sf::Keyboard::F3] = getKeycode(XK_F3); + keycodeMap[sf::Keyboard::F4] = getKeycode(XK_F4); + keycodeMap[sf::Keyboard::F5] = getKeycode(XK_F5); + keycodeMap[sf::Keyboard::F6] = getKeycode(XK_F6); + keycodeMap[sf::Keyboard::F7] = getKeycode(XK_F7); + keycodeMap[sf::Keyboard::F8] = getKeycode(XK_F8); + keycodeMap[sf::Keyboard::F9] = getKeycode(XK_F9); + keycodeMap[sf::Keyboard::F10] = getKeycode(XK_F10); + keycodeMap[sf::Keyboard::F11] = getKeycode(XK_F11); + keycodeMap[sf::Keyboard::F12] = getKeycode(XK_F12); + keycodeMap[sf::Keyboard::F13] = getKeycode(XK_F13); + keycodeMap[sf::Keyboard::F14] = getKeycode(XK_F14); + keycodeMap[sf::Keyboard::F15] = getKeycode(XK_F15); + keycodeMap[sf::Keyboard::Pause] = getKeycode(XK_Pause); + + mapBuilt = true; + } +} namespace sf @@ -39,143 +176,47 @@ namespace priv //////////////////////////////////////////////////////////// bool InputImpl::isKeyPressed(Keyboard::Key key) { - // Get the corresponding X11 keysym - KeySym keysym = 0; - switch (key) - { - case Keyboard::A: keysym = XK_A; break; - case Keyboard::B: keysym = XK_B; break; - case Keyboard::C: keysym = XK_C; break; - case Keyboard::D: keysym = XK_D; break; - case Keyboard::E: keysym = XK_E; break; - case Keyboard::F: keysym = XK_F; break; - case Keyboard::G: keysym = XK_G; break; - case Keyboard::H: keysym = XK_H; break; - case Keyboard::I: keysym = XK_I; break; - case Keyboard::J: keysym = XK_J; break; - case Keyboard::K: keysym = XK_K; break; - case Keyboard::L: keysym = XK_L; break; - case Keyboard::M: keysym = XK_M; break; - case Keyboard::N: keysym = XK_N; break; - case Keyboard::O: keysym = XK_O; break; - case Keyboard::P: keysym = XK_P; break; - case Keyboard::Q: keysym = XK_Q; break; - case Keyboard::R: keysym = XK_R; break; - case Keyboard::S: keysym = XK_S; break; - case Keyboard::T: keysym = XK_T; break; - case Keyboard::U: keysym = XK_U; break; - case Keyboard::V: keysym = XK_V; break; - case Keyboard::W: keysym = XK_W; break; - case Keyboard::X: keysym = XK_X; break; - case Keyboard::Y: keysym = XK_Y; break; - case Keyboard::Z: keysym = XK_Z; break; - case Keyboard::Num0: keysym = XK_0; break; - case Keyboard::Num1: keysym = XK_1; break; - case Keyboard::Num2: keysym = XK_2; break; - case Keyboard::Num3: keysym = XK_3; break; - case Keyboard::Num4: keysym = XK_4; break; - case Keyboard::Num5: keysym = XK_5; break; - case Keyboard::Num6: keysym = XK_6; break; - case Keyboard::Num7: keysym = XK_7; break; - case Keyboard::Num8: keysym = XK_8; break; - case Keyboard::Num9: keysym = XK_9; break; - case Keyboard::Escape: keysym = XK_Escape; break; - case Keyboard::LControl: keysym = XK_Control_L; break; - case Keyboard::LShift: keysym = XK_Shift_L; break; - case Keyboard::LAlt: keysym = XK_Alt_L; break; - case Keyboard::LSystem: keysym = XK_Super_L; break; - case Keyboard::RControl: keysym = XK_Control_R; break; - case Keyboard::RShift: keysym = XK_Shift_R; break; - case Keyboard::RAlt: keysym = XK_Alt_R; break; - case Keyboard::RSystem: keysym = XK_Super_R; break; - case Keyboard::Menu: keysym = XK_Menu; break; - case Keyboard::LBracket: keysym = XK_bracketleft; break; - case Keyboard::RBracket: keysym = XK_bracketright; break; - case Keyboard::SemiColon: keysym = XK_semicolon; break; - case Keyboard::Comma: keysym = XK_comma; break; - case Keyboard::Period: keysym = XK_period; break; - case Keyboard::Quote: keysym = XK_dead_acute; break; - case Keyboard::Slash: keysym = XK_slash; break; - case Keyboard::BackSlash: keysym = XK_backslash; break; - case Keyboard::Tilde: keysym = XK_dead_grave; break; - case Keyboard::Equal: keysym = XK_equal; break; - case Keyboard::Dash: keysym = XK_minus; break; - case Keyboard::Space: keysym = XK_space; break; - case Keyboard::Return: keysym = XK_Return; break; - case Keyboard::BackSpace: keysym = XK_BackSpace; break; - case Keyboard::Tab: keysym = XK_Tab; break; - case Keyboard::PageUp: keysym = XK_Prior; break; - case Keyboard::PageDown: keysym = XK_Next; break; - case Keyboard::End: keysym = XK_End; break; - case Keyboard::Home: keysym = XK_Home; break; - case Keyboard::Insert: keysym = XK_Insert; break; - case Keyboard::Delete: keysym = XK_Delete; break; - case Keyboard::Add: keysym = XK_KP_Add; break; - case Keyboard::Subtract: keysym = XK_KP_Subtract; break; - case Keyboard::Multiply: keysym = XK_KP_Multiply; break; - case Keyboard::Divide: keysym = XK_KP_Divide; break; - case Keyboard::Left: keysym = XK_Left; break; - case Keyboard::Right: keysym = XK_Right; break; - case Keyboard::Up: keysym = XK_Up; break; - case Keyboard::Down: keysym = XK_Down; break; - case Keyboard::Numpad0: keysym = XK_KP_0; break; - case Keyboard::Numpad1: keysym = XK_KP_1; break; - case Keyboard::Numpad2: keysym = XK_KP_2; break; - case Keyboard::Numpad3: keysym = XK_KP_3; break; - case Keyboard::Numpad4: keysym = XK_KP_4; break; - case Keyboard::Numpad5: keysym = XK_KP_5; break; - case Keyboard::Numpad6: keysym = XK_KP_6; break; - case Keyboard::Numpad7: keysym = XK_KP_7; break; - case Keyboard::Numpad8: keysym = XK_KP_8; break; - case Keyboard::Numpad9: keysym = XK_KP_9; break; - case Keyboard::F1: keysym = XK_F1; break; - case Keyboard::F2: keysym = XK_F2; break; - case Keyboard::F3: keysym = XK_F3; break; - case Keyboard::F4: keysym = XK_F4; break; - case Keyboard::F5: keysym = XK_F5; break; - case Keyboard::F6: keysym = XK_F6; break; - case Keyboard::F7: keysym = XK_F7; break; - case Keyboard::F8: keysym = XK_F8; break; - case Keyboard::F9: keysym = XK_F9; break; - case Keyboard::F10: keysym = XK_F10; break; - case Keyboard::F11: keysym = XK_F11; break; - case Keyboard::F12: keysym = XK_F12; break; - case Keyboard::F13: keysym = XK_F13; break; - case Keyboard::F14: keysym = XK_F14; break; - case Keyboard::F15: keysym = XK_F15; break; - case Keyboard::Pause: keysym = XK_Pause; break; - default: keysym = 0; break; - } + if (!mapBuilt) + buildMap(); - // Open a connection with the X server - Display* display = OpenDisplay(); + // Sanity checks + if (key < 0 || key >= sf::Keyboard::KeyCount) + return false; // Convert to keycode - KeyCode keycode = XKeysymToKeycode(display, keysym); - if (keycode != 0) - { - // Get the whole keyboard state - char keys[32]; - XQueryKeymap(display, keys); + xcb_keycode_t keycode = keycodeMap[key]; - // Close the connection with the X server - CloseDisplay(display); + ScopedXcbPtr<xcb_generic_error_t> error(NULL); - // Check our keycode - return (keys[keycode / 8] & (1 << (keycode % 8))) != 0; - } - else + // Open a connection with the X server + xcb_connection_t* connection = OpenConnection(); + + // Get the whole keyboard state + ScopedXcbPtr<xcb_query_keymap_reply_t> keymap( + xcb_query_keymap_reply( + connection, + xcb_query_keymap(connection), + &error + ) + ); + + // Close the connection with the X server + CloseConnection(connection); + + if (error) { - // Close the connection with the X server - CloseDisplay(display); + err() << "Failed to query keymap" << std::endl; return false; } + + // Check our keycode + return (keymap->keys[keycode / 8] & (1 << (keycode % 8))) != 0; } //////////////////////////////////////////////////////////// -void InputImpl::setVirtualKeyboardVisible(bool visible) +void InputImpl::setVirtualKeyboardVisible(bool /*visible*/) { // Not applicable } @@ -185,30 +226,43 @@ void InputImpl::setVirtualKeyboardVisible(bool visible) bool InputImpl::isMouseButtonPressed(Mouse::Button button) { // Open a connection with the X server - Display* display = OpenDisplay(); + xcb_connection_t* connection = OpenConnection(); + + ScopedXcbPtr<xcb_generic_error_t> error(NULL); + + // Get pointer mask + ScopedXcbPtr<xcb_query_pointer_reply_t> pointer( + xcb_query_pointer_reply( + connection, + xcb_query_pointer( + connection, + XCBDefaultRootWindow(connection) + ), + &error + ) + ); - // we don't care about these but they are required - ::Window root, child; - int wx, wy; - int gx, gy; + // Close the connection with the X server + CloseConnection(connection); - unsigned int buttons = 0; - XQueryPointer(display, DefaultRootWindow(display), &root, &child, &gx, &gy, &wx, &wy, &buttons); + if (error) + { + err() << "Failed to query pointer" << std::endl; - // Close the connection with the X server - CloseDisplay(display); + return false; + } + + uint16_t buttons = pointer->mask; switch (button) { - case Mouse::Left: return buttons & Button1Mask; - case Mouse::Right: return buttons & Button3Mask; - case Mouse::Middle: return buttons & Button2Mask; + case Mouse::Left: return buttons & XCB_BUTTON_MASK_1; + case Mouse::Right: return buttons & XCB_BUTTON_MASK_3; + case Mouse::Middle: return buttons & XCB_BUTTON_MASK_2; case Mouse::XButton1: return false; // not supported by X case Mouse::XButton2: return false; // not supported by X default: return false; } - - return false; } @@ -216,21 +270,32 @@ bool InputImpl::isMouseButtonPressed(Mouse::Button button) Vector2i InputImpl::getMousePosition() { // Open a connection with the X server - Display* display = OpenDisplay(); + xcb_connection_t* connection = OpenConnection(); - // we don't care about these but they are required - ::Window root, child; - int x, y; - unsigned int buttons; + ScopedXcbPtr<xcb_generic_error_t> error(NULL); - int gx = 0; - int gy = 0; - XQueryPointer(display, DefaultRootWindow(display), &root, &child, &gx, &gy, &x, &y, &buttons); + ScopedXcbPtr<xcb_query_pointer_reply_t> pointer( + xcb_query_pointer_reply( + connection, + xcb_query_pointer( + connection, + XCBDefaultRootWindow(connection) + ), + &error + ) + ); // Close the connection with the X server - CloseDisplay(display); + CloseConnection(connection); - return Vector2i(gx, gy); + if (error) + { + err() << "Failed to query pointer" << std::endl; + + return Vector2i(0, 0); + } + + return Vector2i(pointer->root_x, pointer->root_y); } @@ -241,21 +306,32 @@ Vector2i InputImpl::getMousePosition(const Window& relativeTo) if (handle) { // Open a connection with the X server - Display* display = OpenDisplay(); + xcb_connection_t* connection = OpenConnection(); - // we don't care about these but they are required - ::Window root, child; - int gx, gy; - unsigned int buttons; + ScopedXcbPtr<xcb_generic_error_t> error(NULL); - int x = 0; - int y = 0; - XQueryPointer(display, handle, &root, &child, &gx, &gy, &x, &y, &buttons); + ScopedXcbPtr<xcb_query_pointer_reply_t> pointer( + xcb_query_pointer_reply( + connection, + xcb_query_pointer( + connection, + handle + ), + &error + ) + ); // Close the connection with the X server - CloseDisplay(display); + CloseConnection(connection); + + if (error) + { + err() << "Failed to query pointer" << std::endl; + + return Vector2i(0, 0); + } - return Vector2i(x, y); + return Vector2i(pointer->win_x, pointer->win_y); } else { @@ -268,13 +344,27 @@ Vector2i InputImpl::getMousePosition(const Window& relativeTo) void InputImpl::setMousePosition(const Vector2i& position) { // Open a connection with the X server - Display* display = OpenDisplay(); + xcb_connection_t* connection = OpenConnection(); + + ScopedXcbPtr<xcb_generic_error_t> error(xcb_request_check( + connection, + xcb_warp_pointer( + connection, + None, // Source window + XCBDefaultRootWindow(connection), // Destination window + 0, 0, // Source position + 0, 0, // Source size + position.x, position.y // Destination position + ) + )); + + if (error) + err() << "Failed to set mouse position" << std::endl; - XWarpPointer(display, None, DefaultRootWindow(display), 0, 0, 0, 0, position.x, position.y); - XFlush(display); + xcb_flush(connection); // Close the connection with the X server - CloseDisplay(display); + CloseConnection(connection); } @@ -282,17 +372,31 @@ void InputImpl::setMousePosition(const Vector2i& position) void InputImpl::setMousePosition(const Vector2i& position, const Window& relativeTo) { // Open a connection with the X server - Display* display = OpenDisplay(); + xcb_connection_t* connection = OpenConnection(); WindowHandle handle = relativeTo.getSystemHandle(); if (handle) { - XWarpPointer(display, None, handle, 0, 0, 0, 0, position.x, position.y); - XFlush(display); + ScopedXcbPtr<xcb_generic_error_t> error(xcb_request_check( + connection, + xcb_warp_pointer( + connection, + None, // Source window + handle, // Destination window + 0, 0, // Source position + 0, 0, // Source size + position.x, position.y // Destination position + ) + )); + + if (error) + err() << "Failed to set mouse position" << std::endl; + + xcb_flush(connection); } // Close the connection with the X server - CloseDisplay(display); + CloseConnection(connection); } diff --git a/src/SFML/Window/Unix/InputImpl.hpp b/src/SFML/Window/Unix/InputImpl.hpp index 32c8f21..9425c3d 100644 --- a/src/SFML/Window/Unix/InputImpl.hpp +++ b/src/SFML/Window/Unix/InputImpl.hpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library -// Copyright (C) 2007-2014 Laurent Gomila (laurent.gom@gmail.com) +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/src/SFML/Window/Unix/JoystickImpl.cpp b/src/SFML/Window/Unix/JoystickImpl.cpp index 0b8d479..d60075d 100644 --- a/src/SFML/Window/Unix/JoystickImpl.cpp +++ b/src/SFML/Window/Unix/JoystickImpl.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library -// Copyright (C) 2007-2014 Laurent Gomila (laurent.gom@gmail.com) +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the use of this software. @@ -27,159 +27,410 @@ //////////////////////////////////////////////////////////// #include <SFML/Window/JoystickImpl.hpp> #include <SFML/System/Err.hpp> -#include <sys/inotify.h> -#include <sys/ioctl.h> -#include <sys/stat.h> -#include <errno.h> +#include <linux/joystick.h> #include <libudev.h> #include <unistd.h> -#include <cstdio> -#include <cstdlib> -#include <sstream> +#include <fcntl.h> +#include <errno.h> +#include <vector> +#include <string> +#include <cstring> namespace { - int notifyFd = -1; - int inputFd = -1; - bool plugged[sf::Joystick::Count]; + udev* udevContext = 0; + udev_monitor* udevMonitor = 0; + + struct JoystickRecord + { + std::string deviceNode; + std::string systemPath; + bool plugged; + }; + + typedef std::vector<JoystickRecord> JoystickList; + JoystickList joystickList; - void updatePluggedList() + bool isJoystick(udev_device* udevDevice) { - udev* udevContext = udev_new(); + // If anything goes wrong, we go safe and return true + + // No device to check, assume not a joystick + if (!udevDevice) + return false; + + const char* devnode = udev_device_get_devnode(udevDevice); + + // We only consider devices with a device node + if (!devnode) + return false; + + // SFML doesn't support evdev yet, so make sure we only handle /js nodes + if (!std::strstr(devnode, "/js")) + return false; - for (unsigned int i = 0; i < sf::Joystick::Count; ++i) + // Check if this device is a joystick + if (udev_device_get_property_value(udevDevice, "ID_INPUT_JOYSTICK")) + return true; + + // Check if this device is something that isn't a joystick + // We do this because the absence of any ID_INPUT_ property doesn't + // necessarily mean that the device isn't a joystick, whereas the + // presence of any ID_INPUT_ property that isn't ID_INPUT_JOYSTICK does + if (udev_device_get_property_value(udevDevice, "ID_INPUT_ACCELEROMETER") || + udev_device_get_property_value(udevDevice, "ID_INPUT_KEY") || + udev_device_get_property_value(udevDevice, "ID_INPUT_KEYBOARD") || + udev_device_get_property_value(udevDevice, "ID_INPUT_MOUSE") || + udev_device_get_property_value(udevDevice, "ID_INPUT_TABLET") || + udev_device_get_property_value(udevDevice, "ID_INPUT_TOUCHPAD") || + udev_device_get_property_value(udevDevice, "ID_INPUT_TOUCHSCREEN")) + return false; + + // On some platforms (older udev), ID_INPUT_ properties are not present, instead + // the system makes use of the ID_CLASS property to identify the device class + const char* idClass = udev_device_get_property_value(udevDevice, "ID_CLASS"); + + if (idClass) { - std::ostringstream name; - name << "js" << i; - std::string nameString = name.str(); + // Check if the device class matches joystick + if (std::strstr(idClass, "joystick")) + return true; + + // Check if the device class matches something that isn't a joystick + // Rationale same as above + if (std::strstr(idClass, "accelerometer") || + std::strstr(idClass, "key") || + std::strstr(idClass, "keyboard") || + std::strstr(idClass, "mouse") || + std::strstr(idClass, "tablet") || + std::strstr(idClass, "touchpad") || + std::strstr(idClass, "touchscreen")) + return false; + } + + // At this point, assume it is a joystick + return true; + } - int file = ::open(("/dev/input/" + nameString).c_str(), O_RDONLY); + void updatePluggedList(udev_device* udevDevice = NULL) + { + if (udevDevice) + { + const char* action = udev_device_get_action(udevDevice); - if (file < 0) + if (action) { - plugged[i] = false; - continue; + if (isJoystick(udevDevice)) + { + // Since isJoystick returned true, this has to succeed + const char* devnode = udev_device_get_devnode(udevDevice); + + JoystickList::iterator record; + + for (record = joystickList.begin(); record != joystickList.end(); ++record) + { + if (record->deviceNode == devnode) + { + if (std::strstr(action, "add")) + { + // The system path might have changed so update it + const char* syspath = udev_device_get_syspath(udevDevice); + + record->plugged = true; + record->systemPath = syspath ? syspath : ""; + break; + } + else if (std::strstr(action, "remove")) + { + record->plugged = false; + break; + } + } + } + + if (record == joystickList.end()) + { + if (std::strstr(action, "add")) + { + // If not mapped before and it got added, map it now + const char* syspath = udev_device_get_syspath(udevDevice); + + JoystickRecord record; + record.deviceNode = devnode; + record.systemPath = syspath ? syspath : ""; + record.plugged = true; + + joystickList.push_back(record); + } + else if (std::strstr(action, "remove")) + { + // Not mapped during the initial scan, and removed (shouldn't happen) + sf::err() << "Trying to disconnect joystick that wasn't connected" << std::endl; + } + } + } + + return; } - ::close(file); + // Do a full rescan if there was no action just to be sure + } - // Check if the device is really a joystick or an - // accelerometer by inspecting whether - // ID_INPUT_ACCELEROMETER is present - if (!udevContext) - { - // Go safe and assume it is if udev isn't available - plugged[i] = true; - continue; - } + // Reset the plugged status of each mapping since we are doing a full rescan + for (JoystickList::iterator record = joystickList.begin(); record != joystickList.end(); ++record) + record->plugged = false; - udev_device* udevDevice = udev_device_new_from_subsystem_sysname(udevContext, "input", nameString.c_str()); + udev_enumerate* udevEnumerator = udev_enumerate_new(udevContext); - if (!udevDevice) - { - // Go safe and assume it is if we can't get the device - plugged[i] = true; - continue; - } + if (!udevEnumerator) + { + sf::err() << "Error while creating udev enumerator" << std::endl; + return; + } - if (udev_device_get_property_value(udevDevice, "ID_INPUT_ACCELEROMETER")) - { - // This device is an accelerometer - plugged[i] = false; - } - else + int result = 0; + + result = udev_enumerate_add_match_subsystem(udevEnumerator, "input"); + + if (result < 0) + { + sf::err() << "Error while adding udev enumerator match" << std::endl; + return; + } + + result = udev_enumerate_scan_devices(udevEnumerator); + + if (result < 0) + { + sf::err() << "Error while enumerating udev devices" << std::endl; + return; + } + + udev_list_entry* devices = udev_enumerate_get_list_entry(udevEnumerator); + udev_list_entry* device; + + udev_list_entry_foreach(device, devices) { + const char* syspath = udev_list_entry_get_name(device); + udev_device* udevDevice = udev_device_new_from_syspath(udevContext, syspath); + + if (udevDevice && isJoystick(udevDevice)) { - // This device is not an accelerometer - // Assume it's a joystick - plugged[i] = true; + // Since isJoystick returned true, this has to succeed + const char* devnode = udev_device_get_devnode(udevDevice); + + JoystickList::iterator record; + + // Check if the device node has been mapped before + for (record = joystickList.begin(); record != joystickList.end(); ++record) + { + if (record->deviceNode == devnode) + { + record->plugged = true; + break; + } + } + + // If not mapped before, map it now + if (record == joystickList.end()) + { + JoystickRecord record; + record.deviceNode = devnode; + record.systemPath = syspath; + record.plugged = true; + + joystickList.push_back(record); + } } udev_device_unref(udevDevice); } - if (udevContext) - udev_unref(udevContext); + udev_enumerate_unref(udevEnumerator); } - bool hasInotifyEvent() + bool hasMonitorEvent() { + // This will not fail since we make sure udevMonitor is valid + int monitorFd = udev_monitor_get_fd(udevMonitor); + fd_set descriptorSet; FD_ZERO(&descriptorSet); - FD_SET(notifyFd, &descriptorSet); + FD_SET(monitorFd, &descriptorSet); timeval timeout = {0, 0}; - return (select(notifyFd + 1, &descriptorSet, NULL, NULL, &timeout) > 0) && - FD_ISSET(notifyFd, &descriptorSet); + return (select(monitorFd + 1, &descriptorSet, NULL, NULL, &timeout) > 0) && + FD_ISSET(monitorFd, &descriptorSet); } - // Get the joystick name - std::string getJoystickName(int file, unsigned int index) + // Get a property value from a udev device + const char* getUdevAttribute(udev_device* udevDevice, const std::string& attributeName) { - // Get the name - char name[128]; + return udev_device_get_property_value(udevDevice, attributeName.c_str()); + } - if (ioctl(file, JSIOCGNAME(sizeof(name)), name) >= 0) - return std::string(name); + // Get a system attribute from a USB device + const char* getUsbAttribute(udev_device* udevDevice, const std::string& attributeName) + { + udev_device* udevDeviceParent = udev_device_get_parent_with_subsystem_devtype(udevDevice, "usb", "usb_device"); - sf::err() << "Unable to get name for joystick at index " << index << std::endl; + if (!udevDeviceParent) + return NULL; - return std::string("Unknown Joystick"); + return udev_device_get_sysattr_value(udevDeviceParent, attributeName.c_str()); } - // Get a system attribute from a udev device as an unsigned int - unsigned int getUdevAttributeUint(udev_device* device, unsigned int index, const std::string& attributeName) + // Get a USB attribute for a joystick as an unsigned int + unsigned int getUsbAttributeUint(udev_device* udevDevice, const std::string& attributeName) { - udev_device* udevDeviceParent = udev_device_get_parent_with_subsystem_devtype(device, "usb", "usb_device"); + if (!udevDevice) + return 0; - if (!udevDeviceParent) + const char* attribute = getUsbAttribute(udevDevice, attributeName); + unsigned int value = 0; + + if (attribute) + value = static_cast<unsigned int>(std::strtoul(attribute, NULL, 16)); + + return value; + } + + // Get a udev property value for a joystick as an unsigned int + unsigned int getUdevAttributeUint(udev_device* udevDevice, const std::string& attributeName) + { + if (!udevDevice) + return 0; + + const char* attribute = getUdevAttribute(udevDevice, attributeName); + unsigned int value = 0; + + if (attribute) + value = static_cast<unsigned int>(std::strtoul(attribute, NULL, 16)); + + return value; + } + + // Get the joystick vendor id + unsigned int getJoystickVendorId(unsigned int index) + { + if (!udevContext) { - sf::err() << "Unable to get joystick attribute. " - << "Could not find parent USB device for joystick at index " << index << "." << std::endl; + sf::err() << "Failed to get vendor ID of joystick " << joystickList[index].deviceNode << std::endl; return 0; } - const char* attributeString = udev_device_get_sysattr_value(udevDeviceParent, attributeName.c_str()); + udev_device* udevDevice = udev_device_new_from_syspath(udevContext, joystickList[index].systemPath.c_str()); - if (!attributeString) + if (!udevDevice) { - sf::err() << "Unable to get joystick attribute '" << attributeName << "'. " - << "Attribute does not exist for joystick at index " << index << "." << std::endl; + sf::err() << "Failed to get vendor ID of joystick " << joystickList[index].deviceNode << std::endl; return 0; } - return static_cast<unsigned int>(std::strtoul(attributeString, NULL, 16)); + unsigned int id = 0; + + // First try using udev + id = getUdevAttributeUint(udevDevice, "ID_VENDOR_ID"); + + if (id) + { + udev_device_unref(udevDevice); + return id; + } + + // Fall back to using USB attribute + id = getUsbAttributeUint(udevDevice, "idVendor"); + + udev_device_unref(udevDevice); + + if (id) + return id; + + sf::err() << "Failed to get vendor ID of joystick " << joystickList[index].deviceNode << std::endl; + + return 0; } - // Get a system attribute for a joystick at index as an unsigned int - unsigned int getAttributeUint(unsigned int index, const std::string& attributeName) + // Get the joystick product id + unsigned int getJoystickProductId(unsigned int index) { - udev* udevContext = udev_new(); - if (!udevContext) { - sf::err() << "Unable to get joystick attribute. " - << "Could not create udev context." << std::endl; + sf::err() << "Failed to get product ID of joystick " << joystickList[index].deviceNode << std::endl; return 0; } - std::ostringstream sysname("js"); - sysname << index; - - udev_device* udevDevice = udev_device_new_from_subsystem_sysname(udevContext, "input", sysname.str().c_str()); + udev_device* udevDevice = udev_device_new_from_syspath(udevContext, joystickList[index].systemPath.c_str()); if (!udevDevice) { - sf::err() << "Unable to get joystick attribute. " - << "Could not find USB device for joystick at index " << index << "." << std::endl; - udev_unref(udevContext); + sf::err() << "Failed to get product ID of joystick " << joystickList[index].deviceNode << std::endl; return 0; } - unsigned int attribute = getUdevAttributeUint(udevDevice, index, attributeName); + unsigned int id = 0; + + // First try using udev + id = getUdevAttributeUint(udevDevice, "ID_MODEL_ID"); + + if (id) + { + udev_device_unref(udevDevice); + return id; + } + + // Fall back to using USB attribute + id = getUsbAttributeUint(udevDevice, "idProduct"); udev_device_unref(udevDevice); - udev_unref(udevContext); - return attribute; + + if (id) + return id; + + sf::err() << "Failed to get product ID of joystick " << joystickList[index].deviceNode << std::endl; + + return 0; + } + + // Get the joystick name + std::string getJoystickName(unsigned int index) + { + std::string devnode = joystickList[index].deviceNode; + + // First try using ioctl with JSIOCGNAME + int fd = ::open(devnode.c_str(), O_RDONLY | O_NONBLOCK); + + if (fd >= 0) + { + // Get the name + char name[128]; + std::memset(name, 0, sizeof(name)); + + int result = ioctl(fd, JSIOCGNAME(sizeof(name)), name); + + ::close(fd); + + if (result >= 0) + return std::string(name); + } + + // Fall back to manual USB chain walk via udev + if (udevContext) + { + udev_device* udevDevice = udev_device_new_from_syspath(udevContext, joystickList[index].systemPath.c_str()); + + if (udevDevice) + { + const char* product = getUsbAttribute(udevDevice, "product"); + udev_device_unref(udevDevice); + + if (product) + return std::string(product); + } + } + + sf::err() << "Unable to get name for joystick " << devnode << std::endl; + + return std::string("Unknown Joystick"); } } @@ -191,95 +442,133 @@ namespace priv //////////////////////////////////////////////////////////// void JoystickImpl::initialize() { - // Reset the array of plugged joysticks - std::fill(plugged, plugged + Joystick::Count, false); + udevContext = udev_new(); - // Do an initial scan - updatePluggedList(); - - // Create the inotify instance - notifyFd = inotify_init(); - if (notifyFd < 0) + if (!udevContext) { - err() << "Failed to initialize inotify, joystick connections and disconnections won't be notified" << std::endl; + sf::err() << "Failed to create udev context, joystick support not available" << std::endl; return; } - // Watch nodes created and deleted in the /dev/input directory - inputFd = inotify_add_watch(notifyFd, "/dev/input", IN_CREATE | IN_DELETE); - if (inputFd < 0) + udevMonitor = udev_monitor_new_from_netlink(udevContext, "udev"); + + if (!udevMonitor) { - err() << "Failed to initialize inotify, joystick connections and disconnections won't be notified" << std::endl; + err() << "Failed to create udev monitor, joystick connections and disconnections won't be notified" << std::endl; + } + else + { + int error = udev_monitor_filter_add_match_subsystem_devtype(udevMonitor, "input", NULL); + + if (error < 0) + { + err() << "Failed to add udev monitor filter, joystick connections and disconnections won't be notified: " << error << std::endl; + + udev_monitor_unref(udevMonitor); + udevMonitor = 0; + } + else + { + error = udev_monitor_enable_receiving(udevMonitor); - // No need to hang on to the inotify handle in this case - ::close(notifyFd); - notifyFd = -1; + if (error < 0) + { + err() << "Failed to enable udev monitor, joystick connections and disconnections won't be notified: " << error << std::endl; + + udev_monitor_unref(udevMonitor); + udevMonitor = 0; + } + } } + + // Do an initial scan + updatePluggedList(); } //////////////////////////////////////////////////////////// void JoystickImpl::cleanup() { - // Stop watching the /dev/input directory - if (inputFd >= 0) - inotify_rm_watch(notifyFd, inputFd); + // Unreference the udev monitor to destroy it + if (udevMonitor) + { + udev_monitor_unref(udevMonitor); + udevMonitor = 0; + } - // Close the inotify file descriptor - if (notifyFd >= 0) - ::close(notifyFd); + // Unreference the udev context to destroy it + if (udevContext) + { + udev_unref(udevContext); + udevContext = 0; + } } //////////////////////////////////////////////////////////// bool JoystickImpl::isConnected(unsigned int index) { - // See if we can skip scanning if inotify is available - if (notifyFd < 0) + // See if we can skip scanning if udev monitor is available + if (!udevMonitor) { - // inotify is not available, perform a scan every query + // udev monitor is not available, perform a scan every query updatePluggedList(); } - else if (hasInotifyEvent()) + else if (hasMonitorEvent()) { // Check if new joysticks were added/removed since last update - // Don't bother decomposing and interpreting the filename, just do a full scan - updatePluggedList(); + udev_device* udevDevice = udev_monitor_receive_device(udevMonitor); - // Flush all the pending events - if (lseek(notifyFd, 0, SEEK_END) < 0) - err() << "Failed to flush inotify of all pending joystick events." << std::endl; + // If we can get the specific device, we check that, + // otherwise just do a full scan if udevDevice == NULL + updatePluggedList(udevDevice); + + if (udevDevice) + udev_device_unref(udevDevice); } + if (index >= joystickList.size()) + return false; + // Then check if the joystick is connected - return plugged[index]; + return joystickList[index].plugged; } //////////////////////////////////////////////////////////// bool JoystickImpl::open(unsigned int index) { - if (plugged[index]) + if (index >= joystickList.size()) + return false; + + if (joystickList[index].plugged) { - std::ostringstream name; - name << "/dev/input/js" << index; + std::string devnode = joystickList[index].deviceNode; // Open the joystick's file descriptor (read-only and non-blocking) - m_file = ::open(name.str().c_str(), O_RDONLY | O_NONBLOCK); + m_file = ::open(devnode.c_str(), O_RDONLY | O_NONBLOCK); if (m_file >= 0) { // Retrieve the axes mapping ioctl(m_file, JSIOCGAXMAP, m_mapping); // Get info - m_identification.name = getJoystickName(m_file, index); - m_identification.vendorId = getAttributeUint(index, "idVendor"); - m_identification.productId = getAttributeUint(index, "idProduct"); + m_identification.name = getJoystickName(index); + + if (udevContext) + { + m_identification.vendorId = getJoystickVendorId(index); + m_identification.productId = getJoystickProductId(index); + } // Reset the joystick state m_state = JoystickState(); return true; } + else + { + err() << "Failed to open joystick " << devnode << ": " << errno << std::endl; + } } return false; @@ -290,6 +579,7 @@ bool JoystickImpl::open(unsigned int index) void JoystickImpl::close() { ::close(m_file); + m_file = -1; } @@ -298,6 +588,9 @@ JoystickCaps JoystickImpl::getCapabilities() const { JoystickCaps caps; + if (m_file < 0) + return caps; + // Get the number of buttons char buttonCount; ioctl(m_file, JSIOCGBUTTONS, &buttonCount); @@ -340,9 +633,16 @@ Joystick::Identification JoystickImpl::getIdentification() const //////////////////////////////////////////////////////////// JoystickState JoystickImpl::JoystickImpl::update() { + if (m_file < 0) + { + m_state = JoystickState(); + return m_state; + } + // pop events from the joystick file js_event joyState; - while (read(m_file, &joyState, sizeof(joyState)) > 0) + int result = read(m_file, &joyState, sizeof(joyState)); + while (result > 0) { switch (joyState.type & ~JS_EVENT_INIT) { @@ -375,10 +675,18 @@ JoystickState JoystickImpl::JoystickImpl::update() break; } } + + result = read(m_file, &joyState, sizeof(joyState)); } - // Check the connection state of the joystick (read() fails with an error != EGAIN if it's no longer connected) - m_state.connected = (errno == EAGAIN); + // Check the connection state of the joystick + // read() returns -1 and errno != EGAIN if it's no longer connected + // We need to check the result of read() as well, since errno could + // have been previously set by some other function call that failed + // result can be either negative or 0 at this point + // If result is 0, assume the joystick is still connected + // If result is negative, check errno and disconnect if it is not EAGAIN + m_state.connected = (!result || (errno == EAGAIN)); return m_state; } diff --git a/src/SFML/Window/Unix/JoystickImpl.hpp b/src/SFML/Window/Unix/JoystickImpl.hpp index 50777ef..34fce2e 100644 --- a/src/SFML/Window/Unix/JoystickImpl.hpp +++ b/src/SFML/Window/Unix/JoystickImpl.hpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library -// Copyright (C) 2007-2014 Laurent Gomila (laurent.gom@gmail.com) +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the use of this software. @@ -28,9 +28,8 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include <linux/joystick.h> -#include <fcntl.h> -#include <string> +#include <SFML/Window/JoystickImpl.hpp> +#include <linux/input.h> namespace sf diff --git a/src/SFML/Window/Unix/ScopedXcbPtr.hpp b/src/SFML/Window/Unix/ScopedXcbPtr.hpp new file mode 100644 index 0000000..6c98065 --- /dev/null +++ b/src/SFML/Window/Unix/ScopedXcbPtr.hpp @@ -0,0 +1,102 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#ifndef SFML_SCOPEDXCBPTR_HPP +#define SFML_SCOPEDXCBPTR_HPP + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include <cstdlib> + + +namespace sf +{ +namespace priv +{ +//////////////////////////////////////////////////////////// +/// \brief Scoped pointer that frees memory returned in XCB replies +/// +//////////////////////////////////////////////////////////// +template<typename T> +class ScopedXcbPtr +{ +public: + //////////////////////////////////////////////////////////// + /// \brief Constructor + /// + /// \param pointer Pointer value to store + /// + //////////////////////////////////////////////////////////// + ScopedXcbPtr(T* pointer); + + //////////////////////////////////////////////////////////// + /// \brief Destructor, calls std::free() on the stored pointer + /// + //////////////////////////////////////////////////////////// + ~ScopedXcbPtr(); + + //////////////////////////////////////////////////////////// + /// \brief Structure dereference operator + /// + /// \return Stored pointer + /// + //////////////////////////////////////////////////////////// + T* operator ->() const; + + //////////////////////////////////////////////////////////// + /// \brief Address operator. + /// + /// \return Address of the stored pointer + /// + //////////////////////////////////////////////////////////// + T** operator &(); + + //////////////////////////////////////////////////////////// + /// \brief Check if stored pointer is valid + /// + /// \return true if stored pointer is valid + /// + //////////////////////////////////////////////////////////// + operator bool() const; + + //////////////////////////////////////////////////////////// + /// \brief Retrieve the stored pointer. + /// + /// \return The stored pointer + /// + //////////////////////////////////////////////////////////// + T* get() const; + +private: + T* m_pointer; ///< Stored pointer +}; + +#include <SFML/Window/Unix/ScopedXcbPtr.inl> + +} // namespace priv + +} // namespace sf + +#endif // SFML_SCOPEDXCBPTR_HPP diff --git a/src/SFML/Window/Unix/ScopedXcbPtr.inl b/src/SFML/Window/Unix/ScopedXcbPtr.inl new file mode 100644 index 0000000..6683a1e --- /dev/null +++ b/src/SFML/Window/Unix/ScopedXcbPtr.inl @@ -0,0 +1,72 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////// +template <typename T> +inline ScopedXcbPtr<T>::ScopedXcbPtr(T* pointer) : +m_pointer(pointer) +{ + +} + + +//////////////////////////////////////////////////////////// +template <typename T> +inline ScopedXcbPtr<T>::~ScopedXcbPtr() +{ + std::free(m_pointer); +} + + +//////////////////////////////////////////////////////////// +template <typename T> +inline T* ScopedXcbPtr<T>::operator ->() const +{ + return m_pointer; +} + + +//////////////////////////////////////////////////////////// +template <typename T> +inline T** ScopedXcbPtr<T>::operator &() +{ + return &m_pointer; +} + + +//////////////////////////////////////////////////////////// +template <typename T> +inline ScopedXcbPtr<T>::operator bool() const +{ + return m_pointer != NULL; +} + + +//////////////////////////////////////////////////////////// +template <typename T> +inline T* ScopedXcbPtr<T>::get() const +{ + return m_pointer; +} diff --git a/src/SFML/Window/Unix/SensorImpl.cpp b/src/SFML/Window/Unix/SensorImpl.cpp index be5e439..144c6d7 100644 --- a/src/SFML/Window/Unix/SensorImpl.cpp +++ b/src/SFML/Window/Unix/SensorImpl.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library -// Copyright (C) 2007-2013 Laurent Gomila (laurent.gom@gmail.com) +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/src/SFML/Window/Unix/SensorImpl.hpp b/src/SFML/Window/Unix/SensorImpl.hpp index bbd705b..49ced87 100644 --- a/src/SFML/Window/Unix/SensorImpl.hpp +++ b/src/SFML/Window/Unix/SensorImpl.hpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library -// Copyright (C) 2007-2013 Laurent Gomila (laurent.gom@gmail.com) +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/src/SFML/Window/Unix/VideoModeImpl.cpp b/src/SFML/Window/Unix/VideoModeImpl.cpp index c7e6204..9ee812f 100644 --- a/src/SFML/Window/Unix/VideoModeImpl.cpp +++ b/src/SFML/Window/Unix/VideoModeImpl.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library -// Copyright (C) 2007-2014 Laurent Gomila (laurent.gom@gmail.com) +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the use of this software. @@ -27,9 +27,9 @@ //////////////////////////////////////////////////////////// #include <SFML/Window/VideoModeImpl.hpp> #include <SFML/Window/Unix/Display.hpp> +#include <SFML/Window/Unix/ScopedXcbPtr.hpp> #include <SFML/System/Err.hpp> -#include <X11/Xlib.h> -#include <X11/extensions/Xrandr.h> +#include <xcb/randr.h> #include <algorithm> @@ -43,73 +43,96 @@ std::vector<VideoMode> VideoModeImpl::getFullscreenModes() std::vector<VideoMode> modes; // Open a connection with the X server - Display* display = OpenDisplay(); - if (display) + xcb_connection_t* connection = OpenConnection(); + + // Retrieve the default screen + xcb_screen_t* screen = XCBDefaultScreen(connection); + + ScopedXcbPtr<xcb_generic_error_t> error(NULL); + + const xcb_query_extension_reply_t* randrExt = xcb_get_extension_data(connection, &xcb_randr_id); + + if (!randrExt || !randrExt->present) { - // Retrieve the default screen number - int screen = DefaultScreen(display); + // Randr extension is not supported: we cannot get the video modes + err() << "Failed to use the RandR extension while trying to get the supported video modes" << std::endl; - // Check if the XRandR extension is present - int version; - if (XQueryExtension(display, "RANDR", &version, &version, &version)) - { - // Get the current configuration - XRRScreenConfiguration* config = XRRGetScreenInfo(display, RootWindow(display, screen)); - if (config) - { - // Get the available screen sizes - int nbSizes; - XRRScreenSize* sizes = XRRConfigSizes(config, &nbSizes); - if (sizes && (nbSizes > 0)) - { - // Get the list of supported depths - int nbDepths = 0; - int* depths = XListDepths(display, screen, &nbDepths); - if (depths && (nbDepths > 0)) - { - // Combine depths and sizes to fill the array of supported modes - for (int i = 0; i < nbDepths; ++i) - { - for (int j = 0; j < nbSizes; ++j) - { - // Convert to VideoMode - VideoMode mode(sizes[j].width, sizes[j].height, depths[i]); - - // Add it only if it is not already in the array - if (std::find(modes.begin(), modes.end(), mode) == modes.end()) - modes.push_back(mode); - } - } - - // Free the array of depths - XFree(depths); - } - } - - // Free the configuration instance - XRRFreeScreenConfigInfo(config); - } - else - { - // Failed to get the screen configuration - err() << "Failed to retrieve the screen configuration while trying to get the supported video modes" << std::endl; - } - } - else - { - // XRandr extension is not supported: we cannot get the video modes - err() << "Failed to use the XRandR extension while trying to get the supported video modes" << std::endl; - } + // Close the connection with the X server + CloseConnection(connection); + + return modes; + } + + // Load RandR and check its version + ScopedXcbPtr<xcb_randr_query_version_reply_t> randrVersion(xcb_randr_query_version_reply( + connection, + xcb_randr_query_version( + connection, + 1, + 1 + ), + &error + )); + + if (error) + { + err() << "Failed to load the RandR extension while trying to get the supported video modes" << std::endl; // Close the connection with the X server - CloseDisplay(display); + CloseConnection(connection); + + return modes; } - else + + // Get the current configuration + ScopedXcbPtr<xcb_randr_get_screen_info_reply_t> config(xcb_randr_get_screen_info_reply( + connection, + xcb_randr_get_screen_info( + connection, + screen->root + ), + &error + )); + + if (error) + { + // Failed to get the screen configuration + err() << "Failed to retrieve the screen configuration while trying to get the supported video modes" << std::endl; + + // Close the connection with the X server + CloseConnection(connection); + + return modes; + } + + // Get the available screen sizes + xcb_randr_screen_size_t* sizes = xcb_randr_get_screen_info_sizes(config.get()); + if (sizes && (config->nSizes > 0)) { - // We couldn't connect to the X server - err() << "Failed to connect to the X server while trying to get the supported video modes" << std::endl; + // Get the list of supported depths + xcb_depth_iterator_t iter = xcb_screen_allowed_depths_iterator(screen); + // Combine depths and sizes to fill the array of supported modes + for (; iter.rem; xcb_depth_next(&iter)) + { + for (int j = 0; j < config->nSizes; ++j) + { + // Convert to VideoMode + VideoMode mode(sizes[j].width, sizes[j].height, iter.data->depth); + + if (config->rotation == XCB_RANDR_ROTATION_ROTATE_90 || + config->rotation == XCB_RANDR_ROTATION_ROTATE_270) + std::swap(mode.width, mode.height); + + // Add it only if it is not already in the array + if (std::find(modes.begin(), modes.end(), mode) == modes.end()) + modes.push_back(mode); + } + } } + // Close the connection with the X server + CloseConnection(connection); + return modes; } @@ -120,54 +143,91 @@ VideoMode VideoModeImpl::getDesktopMode() VideoMode desktopMode; // Open a connection with the X server - Display* display = OpenDisplay(); - if (display) + xcb_connection_t* connection = OpenConnection(); + + // Retrieve the default screen + xcb_screen_t* screen = XCBDefaultScreen(connection); + + ScopedXcbPtr<xcb_generic_error_t> error(NULL); + + // Check if the RandR extension is present + const xcb_query_extension_reply_t* randrExt = xcb_get_extension_data(connection, &xcb_randr_id); + + if (!randrExt || !randrExt->present) { - // Retrieve the default screen number - int screen = DefaultScreen(display); + // Randr extension is not supported: we cannot get the video modes + err() << "Failed to use the RandR extension while trying to get the desktop video mode" << std::endl; - // Check if the XRandR extension is present - int version; - if (XQueryExtension(display, "RANDR", &version, &version, &version)) - { - // Get the current configuration - XRRScreenConfiguration* config = XRRGetScreenInfo(display, RootWindow(display, screen)); - if (config) - { - // Get the current video mode - Rotation currentRotation; - int currentMode = XRRConfigCurrentConfiguration(config, ¤tRotation); - - // Get the available screen sizes - int nbSizes; - XRRScreenSize* sizes = XRRConfigSizes(config, &nbSizes); - if (sizes && (nbSizes > 0)) - desktopMode = VideoMode(sizes[currentMode].width, sizes[currentMode].height, DefaultDepth(display, screen)); - - // Free the configuration instance - XRRFreeScreenConfigInfo(config); - } - else - { - // Failed to get the screen configuration - err() << "Failed to retrieve the screen configuration while trying to get the desktop video modes" << std::endl; - } - } - else - { - // XRandr extension is not supported: we cannot get the video modes - err() << "Failed to use the XRandR extension while trying to get the desktop video modes" << std::endl; - } + // Close the connection with the X server + CloseConnection(connection); + + return desktopMode; + } + + // Load RandR and check its version + ScopedXcbPtr<xcb_randr_query_version_reply_t> randrVersion(xcb_randr_query_version_reply( + connection, + xcb_randr_query_version( + connection, + 1, + 1 + ), + &error + )); + + if (error) + { + err() << "Failed to load the RandR extension while trying to get the desktop video mode" << std::endl; // Close the connection with the X server - CloseDisplay(display); + CloseConnection(connection); + + return desktopMode; + } + + // Get the current configuration + ScopedXcbPtr<xcb_randr_get_screen_info_reply_t> config(xcb_randr_get_screen_info_reply( + connection, + xcb_randr_get_screen_info( + connection, + screen->root + ), + &error + )); + + if (error) + { + // Failed to get the screen configuration + err() << "Failed to retrieve the screen configuration while trying to get the desktop video mode" << std::endl; + + // Close the connection with the X server + CloseConnection(connection); + + return desktopMode; + } + + // Get the current video mode + xcb_randr_mode_t currentMode = config->sizeID; + + // Get the available screen sizes + int nbSizes = xcb_randr_get_screen_info_sizes_length(config.get()); + xcb_randr_screen_size_t* sizes = xcb_randr_get_screen_info_sizes(config.get()); + if (sizes && (nbSizes > 0)) + { + desktopMode = VideoMode(sizes[currentMode].width, sizes[currentMode].height, screen->root_depth); + + if (config->rotation == XCB_RANDR_ROTATION_ROTATE_90 || + config->rotation == XCB_RANDR_ROTATION_ROTATE_270) + std::swap(desktopMode.width, desktopMode.height); } else { - // We couldn't connect to the X server - err() << "Failed to connect to the X server while trying to get the desktop video modes" << std::endl; + err() << "Failed to retrieve any screen sizes while trying to get the desktop video mode" << std::endl; } + // Close the connection with the X server + CloseConnection(connection); + return desktopMode; } diff --git a/src/SFML/Window/Unix/WindowImplX11.cpp b/src/SFML/Window/Unix/WindowImplX11.cpp index 9e29757..d8c26e0 100644 --- a/src/SFML/Window/Unix/WindowImplX11.cpp +++ b/src/SFML/Window/Unix/WindowImplX11.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library -// Copyright (C) 2007-2014 Laurent Gomila (laurent.gom@gmail.com) +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the use of this software. @@ -28,19 +28,27 @@ #include <SFML/Window/WindowStyle.hpp> // important to be included first (conflict with None) #include <SFML/Window/Unix/WindowImplX11.hpp> #include <SFML/Window/Unix/Display.hpp> +#include <SFML/Window/Unix/ScopedXcbPtr.hpp> #include <SFML/System/Utf.hpp> #include <SFML/System/Err.hpp> -#include <X11/Xutil.h> -#include <X11/keysym.h> -#include <X11/extensions/Xrandr.h> -#include <libgen.h> +#include <SFML/System/Mutex.hpp> +#include <SFML/System/Lock.hpp> +#include <xcb/xcb_image.h> +#include <xcb/randr.h> +#include <X11/Xlibint.h> +#include <sys/types.h> +#include <sys/stat.h> #include <unistd.h> -#include <cstring> -#include <sstream> +#include <libgen.h> +#include <fcntl.h> +#include <algorithm> #include <vector> #include <string> -#include <iterator> -#include <algorithm> +#include <cstring> + +// So we don't have to require xcb dri2 to be present +#define XCB_DRI2_BUFFER_SWAP_COMPLETE 0 +#define XCB_DRI2_INVALIDATE_BUFFERS 1 #ifdef SFML_OPENGL_ES #include <SFML/Window/EglContext.hpp> @@ -57,30 +65,430 @@ namespace { sf::priv::WindowImplX11* fullscreenWindow = NULL; std::vector<sf::priv::WindowImplX11*> allWindows; - unsigned long eventMask = FocusChangeMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | - PointerMotionMask | KeyPressMask | KeyReleaseMask | StructureNotifyMask | - EnterWindowMask | LeaveWindowMask; + sf::Mutex allWindowsMutex; + sf::String windowManagerName; - // Filter the events received by windows (only allow those matching a specific window) - Bool checkEvent(::Display*, XEvent* event, XPointer userData) - { - // Just check if the event matches the window - return event->xany.window == reinterpret_cast< ::Window >(userData); - } + bool mapBuilt = false; + + // We use a simple array instead of a map => constant time lookup + // xcb_keycode_t can only contain 256 distinct values + sf::Keyboard::Key sfKeyMap[256]; + + static const unsigned long eventMask = XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_BUTTON_PRESS | + XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION | + XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_KEY_PRESS | + XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | + XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW; // Find the name of the current executable - void findExecutableName(char* buffer, std::size_t bufferSize) + std::string findExecutableName() { - //Default fallback name - const char* executableName = "sfml"; - std::size_t length = readlink("/proc/self/exe", buffer, bufferSize); - if ((length > 0) && (length < bufferSize)) + // We use /proc/self/cmdline to get the command line + // the user used to invoke this instance of the application + int file = ::open("/proc/self/cmdline", O_RDONLY | O_NONBLOCK); + + if (file < 0) + return "sfml"; + + std::vector<char> buffer(256, 0); + std::size_t offset = 0; + ssize_t result = 0; + + while ((result = read(file, &buffer[offset], 256)) > 0) + { + buffer.resize(buffer.size() + result, 0); + offset += result; + } + + ::close(file); + + if (offset) { + buffer[offset] = 0; + // Remove the path to keep the executable name only - buffer[length] = '\0'; - executableName = basename(buffer); + return basename(&buffer[0]); + } + + // Default fallback name + return "sfml"; + } + + // Check if Extended Window Manager Hints are supported + bool ewmhSupported() + { + static bool checked = false; + static bool ewmhSupported = false; + + if (checked) + return ewmhSupported; + + checked = true; + + xcb_connection_t* connection = sf::priv::OpenConnection(); + + xcb_atom_t netSupportingWmCheck = sf::priv::getAtom("_NET_SUPPORTING_WM_CHECK", true); + xcb_atom_t netSupported = sf::priv::getAtom("_NET_SUPPORTED", true); + + if (!netSupportingWmCheck || !netSupported) + return false; + + sf::priv::ScopedXcbPtr<xcb_generic_error_t> error(NULL); + + sf::priv::ScopedXcbPtr<xcb_get_property_reply_t> rootSupportingWindow(xcb_get_property_reply( + connection, + xcb_get_property( + connection, + 0, + sf::priv::XCBDefaultRootWindow(connection), + netSupportingWmCheck, + XCB_ATOM_WINDOW, + 0, + 1 + ), + &error + )); + + if (!rootSupportingWindow || rootSupportingWindow->length != 1) + { + sf::priv::CloseConnection(connection); + return false; + } + + xcb_window_t* rootWindow = reinterpret_cast<xcb_window_t*>(xcb_get_property_value(rootSupportingWindow.get())); + + if (!rootWindow) + { + sf::priv::CloseConnection(connection); + return false; + } + + sf::priv::ScopedXcbPtr<xcb_get_property_reply_t> childSupportingWindow(xcb_get_property_reply( + connection, + xcb_get_property( + connection, + 0, + *rootWindow, + netSupportingWmCheck, + XCB_ATOM_WINDOW, + 0, + 1 + ), + &error + )); + + if (!childSupportingWindow || childSupportingWindow->length != 1) + { + sf::priv::CloseConnection(connection); + return false; + } + + xcb_window_t* childWindow = reinterpret_cast<xcb_window_t*>(xcb_get_property_value(childSupportingWindow.get())); + + if (!childWindow) + { + sf::priv::CloseConnection(connection); + return false; + } + + // Conforming window managers should return the same window for both queries + if (*rootWindow != *childWindow) + { + sf::priv::CloseConnection(connection); + return false; + } + + ewmhSupported = true; + + // We try to get the name of the window manager + // for window manager specific workarounds + xcb_atom_t netWmName = sf::priv::getAtom("_NET_WM_NAME", true); + xcb_atom_t utf8StringType = sf::priv::getAtom("UTF8_STRING"); + + if (!utf8StringType) + utf8StringType = XCB_ATOM_STRING; + + if (!netWmName) + { + sf::priv::CloseConnection(connection); + return true; + } + + sf::priv::ScopedXcbPtr<xcb_get_property_reply_t> wmName(xcb_get_property_reply( + connection, + xcb_get_property( + connection, + 0, + *childWindow, + netWmName, + utf8StringType, + 0, + 0x7fffffff + ), + &error + )); + + sf::priv::CloseConnection(connection); + + const char* name = reinterpret_cast<const char*>(xcb_get_property_value(wmName.get())); + + windowManagerName = name; + + return true; + } + + xcb_query_extension_reply_t getDriExtension() + { + xcb_connection_t* connection = sf::priv::OpenConnection(); + + sf::priv::ScopedXcbPtr<xcb_generic_error_t> error(NULL); + + // Check if the DRI2 extension is present + // We don't use xcb_get_extension_data here to avoid having to link to xcb_dri2 + static const std::string DRI2 = "DRI2"; + sf::priv::ScopedXcbPtr<xcb_query_extension_reply_t> driExt(xcb_query_extension_reply( + connection, + xcb_query_extension( + connection, + DRI2.size(), + DRI2.c_str() + ), + &error + )); + + // Close the connection with the X server + sf::priv::CloseConnection(connection); + + if (error || !driExt || !driExt->present) + { + xcb_query_extension_reply_t reply; + std::memset(&reply, 0, sizeof(reply)); + return reply; + } + + return *driExt.get(); + } + + void dumpXcbExtensions() + { + xcb_connection_t* connection = sf::priv::OpenConnection(); + + sf::priv::ScopedXcbPtr<xcb_generic_error_t> error(NULL); + + // Check if the RandR extension is present + sf::priv::ScopedXcbPtr<xcb_list_extensions_reply_t> extensions(xcb_list_extensions_reply( + connection, + xcb_list_extensions( + connection + ), + &error + )); + + if (error || !extensions) + { + // Close the connection with the X server + sf::priv::CloseConnection(connection); + + sf::err() << "Couldn't get list of X extensions" << std::endl; + return; + } + + xcb_str_iterator_t iter = xcb_list_extensions_names_iterator(extensions.get()); + + sf::err() << "X Extensions:" << std::endl; + + while (iter.rem) + { + std::string name(xcb_str_name(iter.data), xcb_str_name_length(iter.data)); + + sf::priv::ScopedXcbPtr<xcb_generic_error_t> error(NULL); + + // Check if the RandR extension is present + sf::priv::ScopedXcbPtr<xcb_query_extension_reply_t> extension(xcb_query_extension_reply( + connection, + xcb_query_extension( + connection, + name.size(), + name.c_str() + ), + &error + )); + + if (error || !extension || !extension->present) + { + sf::err() << "\t" << name << " - Failed to query" << std::endl; + continue; + } + + int firstEvent = extension->first_event; + + sf::err() << "\t" << name << " - First event: " << firstEvent << std::endl; + iter.data += xcb_str_name_length(iter.data) + 1; + --iter.rem; + } + + // Close the connection with the X server + sf::priv::CloseConnection(connection); + } + + void dumpUnhandledEvent(uint8_t type) + { + static std::vector<uint8_t> types; + + // Check if we already reported this type + if (std::find(types.begin(), types.end(), type) != types.end()) + return; + + // Insert it if new + types.push_back(type); + + static bool dumpedExtensions = false; + + if (!dumpedExtensions) + { + dumpXcbExtensions(); + dumpedExtensions = true; + } + + sf::err() << "Unhandled event type: " << (static_cast<int>(type) & ~0x80) << std::endl + << "Report this to the SFML maintainers if possible" << std::endl; + } + + xcb_keysym_t getKeysymFromKeycode(xcb_keycode_t keycode) + { + return sf::priv::getKeysymMap()[keycode]; + } + + sf::Keyboard::Key keysymToSF(xcb_keysym_t symbol) + { + switch (symbol) + { + case XK_Shift_L: return sf::Keyboard::LShift; + case XK_Shift_R: return sf::Keyboard::RShift; + case XK_Control_L: return sf::Keyboard::LControl; + case XK_Control_R: return sf::Keyboard::RControl; + case XK_Alt_L: return sf::Keyboard::LAlt; + case XK_Alt_R: return sf::Keyboard::RAlt; + case XK_Super_L: return sf::Keyboard::LSystem; + case XK_Super_R: return sf::Keyboard::RSystem; + case XK_Menu: return sf::Keyboard::Menu; + case XK_Escape: return sf::Keyboard::Escape; + case XK_semicolon: return sf::Keyboard::SemiColon; + case XK_slash: return sf::Keyboard::Slash; + case XK_equal: return sf::Keyboard::Equal; + case XK_minus: return sf::Keyboard::Dash; + case XK_bracketleft: return sf::Keyboard::LBracket; + case XK_bracketright: return sf::Keyboard::RBracket; + case XK_comma: return sf::Keyboard::Comma; + case XK_period: return sf::Keyboard::Period; + case XK_apostrophe: return sf::Keyboard::Quote; + case XK_backslash: return sf::Keyboard::BackSlash; + case XK_grave: return sf::Keyboard::Tilde; + case XK_space: return sf::Keyboard::Space; + case XK_Return: return sf::Keyboard::Return; + case XK_KP_Enter: return sf::Keyboard::Return; + case XK_BackSpace: return sf::Keyboard::BackSpace; + case XK_Tab: return sf::Keyboard::Tab; + case XK_Prior: return sf::Keyboard::PageUp; + case XK_Next: return sf::Keyboard::PageDown; + case XK_End: return sf::Keyboard::End; + case XK_Home: return sf::Keyboard::Home; + case XK_Insert: return sf::Keyboard::Insert; + case XK_Delete: return sf::Keyboard::Delete; + case XK_KP_Add: return sf::Keyboard::Add; + case XK_KP_Subtract: return sf::Keyboard::Subtract; + case XK_KP_Multiply: return sf::Keyboard::Multiply; + case XK_KP_Divide: return sf::Keyboard::Divide; + case XK_Pause: return sf::Keyboard::Pause; + case XK_F1: return sf::Keyboard::F1; + case XK_F2: return sf::Keyboard::F2; + case XK_F3: return sf::Keyboard::F3; + case XK_F4: return sf::Keyboard::F4; + case XK_F5: return sf::Keyboard::F5; + case XK_F6: return sf::Keyboard::F6; + case XK_F7: return sf::Keyboard::F7; + case XK_F8: return sf::Keyboard::F8; + case XK_F9: return sf::Keyboard::F9; + case XK_F10: return sf::Keyboard::F10; + case XK_F11: return sf::Keyboard::F11; + case XK_F12: return sf::Keyboard::F12; + case XK_F13: return sf::Keyboard::F13; + case XK_F14: return sf::Keyboard::F14; + case XK_F15: return sf::Keyboard::F15; + case XK_Left: return sf::Keyboard::Left; + case XK_Right: return sf::Keyboard::Right; + case XK_Up: return sf::Keyboard::Up; + case XK_Down: return sf::Keyboard::Down; + case XK_KP_0: return sf::Keyboard::Numpad0; + case XK_KP_1: return sf::Keyboard::Numpad1; + case XK_KP_2: return sf::Keyboard::Numpad2; + case XK_KP_3: return sf::Keyboard::Numpad3; + case XK_KP_4: return sf::Keyboard::Numpad4; + case XK_KP_5: return sf::Keyboard::Numpad5; + case XK_KP_6: return sf::Keyboard::Numpad6; + case XK_KP_7: return sf::Keyboard::Numpad7; + case XK_KP_8: return sf::Keyboard::Numpad8; + case XK_KP_9: return sf::Keyboard::Numpad9; + case XK_a: return sf::Keyboard::A; + case XK_b: return sf::Keyboard::B; + case XK_c: return sf::Keyboard::C; + case XK_d: return sf::Keyboard::D; + case XK_e: return sf::Keyboard::E; + case XK_f: return sf::Keyboard::F; + case XK_g: return sf::Keyboard::G; + case XK_h: return sf::Keyboard::H; + case XK_i: return sf::Keyboard::I; + case XK_j: return sf::Keyboard::J; + case XK_k: return sf::Keyboard::K; + case XK_l: return sf::Keyboard::L; + case XK_m: return sf::Keyboard::M; + case XK_n: return sf::Keyboard::N; + case XK_o: return sf::Keyboard::O; + case XK_p: return sf::Keyboard::P; + case XK_q: return sf::Keyboard::Q; + case XK_r: return sf::Keyboard::R; + case XK_s: return sf::Keyboard::S; + case XK_t: return sf::Keyboard::T; + case XK_u: return sf::Keyboard::U; + case XK_v: return sf::Keyboard::V; + case XK_w: return sf::Keyboard::W; + case XK_x: return sf::Keyboard::X; + case XK_y: return sf::Keyboard::Y; + case XK_z: return sf::Keyboard::Z; + case XK_0: return sf::Keyboard::Num0; + case XK_1: return sf::Keyboard::Num1; + case XK_2: return sf::Keyboard::Num2; + case XK_3: return sf::Keyboard::Num3; + case XK_4: return sf::Keyboard::Num4; + case XK_5: return sf::Keyboard::Num5; + case XK_6: return sf::Keyboard::Num6; + case XK_7: return sf::Keyboard::Num7; + case XK_8: return sf::Keyboard::Num8; + case XK_9: return sf::Keyboard::Num9; + } + + return sf::Keyboard::Unknown; + } + + void buildMap() + { + for (xcb_keycode_t i = 0; ; ++i) + { + sfKeyMap[i] = keysymToSF(getKeysymFromKeycode(i)); + + if (i == 255) + break; } - std::memmove(buffer, executableName, std::strlen(executableName) + 1); + + mapBuilt = true; + } + + sf::Keyboard::Key keycodeToSF(xcb_keycode_t keycode) + { + if (!mapBuilt) + buildMap(); + + return sfKeyMap[keycode]; } } @@ -91,20 +499,33 @@ namespace priv { //////////////////////////////////////////////////////////// WindowImplX11::WindowImplX11(WindowHandle handle) : -m_window (0), -m_inputMethod (NULL), -m_inputContext(NULL), -m_isExternal (true), -m_atomClose (0), -m_oldVideoMode(-1), -m_hiddenCursor(0), -m_keyRepeat (true), -m_previousSize(-1, -1), -m_useSizeHints(false) +m_window (0), +m_inputMethod (NULL), +m_inputContext (NULL), +m_isExternal (true), +m_hiddenCursor (0), +m_keyRepeat (true), +m_previousSize (-1, -1), +m_useSizeHints (false), +m_fullscreen (false) { // Open a connection with the X server m_display = OpenDisplay(); - m_screen = DefaultScreen(m_display); + m_connection = XGetXCBConnection(m_display); + + std::memset(&m_oldVideoMode, 0, sizeof(m_oldVideoMode)); + + if (!m_connection) + { + err() << "Failed cast Display object to an XCB connection object" << std::endl; + return; + } + + // Make sure to check for EWMH support before we do anything + ewmhSupported(); + + m_screen = XCBDefaultScreen(m_connection); + XSetEventQueueOwner(m_display, XCBOwnsEventQueue); // Save the window handle m_window = handle; @@ -112,7 +533,17 @@ m_useSizeHints(false) if (m_window) { // Make sure the window is listening to all the required events - XSelectInput(m_display, m_window, eventMask & ~ButtonPressMask); + const uint32_t value_list[] = {static_cast<uint32_t>(eventMask)}; + + xcb_change_window_attributes( + m_connection, + m_window, + XCB_CW_EVENT_MASK, + value_list + ); + + // Set the WM protocols + setProtocols(); // Do some common initializations initialize(); @@ -122,158 +553,139 @@ m_useSizeHints(false) //////////////////////////////////////////////////////////// WindowImplX11::WindowImplX11(VideoMode mode, const String& title, unsigned long style, const ContextSettings& settings) : -m_window (0), -m_inputMethod (NULL), -m_inputContext(NULL), -m_isExternal (false), -m_atomClose (0), -m_oldVideoMode(-1), -m_hiddenCursor(0), -m_keyRepeat (true), -m_previousSize(-1, -1), -m_useSizeHints(false) +m_window (0), +m_inputMethod (NULL), +m_inputContext (NULL), +m_isExternal (false), +m_hiddenCursor (0), +m_keyRepeat (true), +m_previousSize (-1, -1), +m_useSizeHints (false), +m_fullscreen ((style & Style::Fullscreen) != 0) { // Open a connection with the X server m_display = OpenDisplay(); - m_screen = DefaultScreen(m_display); - ::Window root = RootWindow(m_display, m_screen); + m_connection = XGetXCBConnection(m_display); - // Compute position and size - int left, top; - bool fullscreen = (style & Style::Fullscreen) != 0; - if (!fullscreen) - { - left = (DisplayWidth(m_display, m_screen) - mode.width) / 2; - top = (DisplayHeight(m_display, m_screen) - mode.height) / 2; - } - else + std::memset(&m_oldVideoMode, 0, sizeof(m_oldVideoMode)); + + if (!m_connection) { - left = 0; - top = 0; + err() << "Failed cast Display object to an XCB connection object" << std::endl; + return; } + + // Make sure to check for EWMH support before we do anything + ewmhSupported(); + + m_screen = XCBDefaultScreen(m_connection); + XSetEventQueueOwner(m_display, XCBOwnsEventQueue); + + // Compute position and size + int left = m_fullscreen ? 0 : (m_screen->width_in_pixels - mode.width) / 2; + int top = m_fullscreen ? 0 : (m_screen->height_in_pixels - mode.height) / 2; int width = mode.width; int height = mode.height; - // Switch to fullscreen if necessary - if (fullscreen) - switchToFullscreen(mode); - // Choose the visual according to the context settings XVisualInfo visualInfo = ContextType::selectBestVisual(m_display, mode.bitsPerPixel, settings); // Define the window attributes - XSetWindowAttributes attributes; - attributes.override_redirect = fullscreen; - attributes.event_mask = eventMask; - attributes.colormap = XCreateColormap(m_display, root, visualInfo.visual, AllocNone); + xcb_colormap_t colormap = xcb_generate_id(m_connection); + xcb_create_colormap(m_connection, XCB_COLORMAP_ALLOC_NONE, colormap, m_screen->root, visualInfo.visualid); + const uint32_t value_list[] = {m_fullscreen && !ewmhSupported(), static_cast<uint32_t>(eventMask), colormap}; // Create the window - m_window = XCreateWindow(m_display, - root, - left, top, - width, height, - 0, - visualInfo.depth, - InputOutput, - visualInfo.visual, - CWEventMask | CWOverrideRedirect | CWColormap, &attributes); - if (!m_window) + m_window = xcb_generate_id(m_connection); + + ScopedXcbPtr<xcb_generic_error_t> errptr(xcb_request_check( + m_connection, + xcb_create_window_checked( + m_connection, + static_cast<uint8_t>(visualInfo.depth), + m_window, + m_screen->root, + left, top, + width, height, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + visualInfo.visualid, + XCB_CW_EVENT_MASK | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_COLORMAP, + value_list + ) + )); + + if (errptr) { err() << "Failed to create window" << std::endl; return; } - // Set the window's name - setTitle(title); - - // Set the window's style (tell the windows manager to change our window's decorations and functions according to the requested style) - if (!fullscreen) + // Set the WM protocols + setProtocols(); + + // Set the WM initial state to the normal state + WMHints hints; + std::memset(&hints, 0, sizeof(hints)); + hints.initial_state = 1; + hints.flags |= 1 << 1; + setWMHints(hints); + + // If not in fullscreen, set the window's style (tell the window manager to + // change our window's decorations and functions according to the requested style) + if (!m_fullscreen) + setMotifHints(style); + + WMSizeHints sizeHints; + std::memset(&sizeHints, 0, sizeof(sizeHints)); + + // This is a hack to force some windows managers to disable resizing + // Fullscreen is bugged on Openbox. Unless size hints are set, there + // will be a region of the window that is off-screen. We try to workaround + // this by setting size hints even in fullscreen just for Openbox. + if ((!m_fullscreen || (windowManagerName == "Openbox")) && !(style & Style::Resize)) { - Atom WMHintsAtom = XInternAtom(m_display, "_MOTIF_WM_HINTS", false); - if (WMHintsAtom) - { - static const unsigned long MWM_HINTS_FUNCTIONS = 1 << 0; - static const unsigned long MWM_HINTS_DECORATIONS = 1 << 1; - - //static const unsigned long MWM_DECOR_ALL = 1 << 0; - static const unsigned long MWM_DECOR_BORDER = 1 << 1; - static const unsigned long MWM_DECOR_RESIZEH = 1 << 2; - static const unsigned long MWM_DECOR_TITLE = 1 << 3; - static const unsigned long MWM_DECOR_MENU = 1 << 4; - static const unsigned long MWM_DECOR_MINIMIZE = 1 << 5; - static const unsigned long MWM_DECOR_MAXIMIZE = 1 << 6; - - //static const unsigned long MWM_FUNC_ALL = 1 << 0; - static const unsigned long MWM_FUNC_RESIZE = 1 << 1; - static const unsigned long MWM_FUNC_MOVE = 1 << 2; - static const unsigned long MWM_FUNC_MINIMIZE = 1 << 3; - static const unsigned long MWM_FUNC_MAXIMIZE = 1 << 4; - static const unsigned long MWM_FUNC_CLOSE = 1 << 5; - - struct WMHints - { - unsigned long flags; - unsigned long functions; - unsigned long decorations; - long inputMode; - unsigned long state; - }; - - WMHints hints; - hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS; - hints.decorations = 0; - hints.functions = 0; - - if (style & Style::Titlebar) - { - hints.decorations |= MWM_DECOR_BORDER | MWM_DECOR_TITLE | MWM_DECOR_MINIMIZE | MWM_DECOR_MENU; - hints.functions |= MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE; - } - if (style & Style::Resize) - { - hints.decorations |= MWM_DECOR_MAXIMIZE | MWM_DECOR_RESIZEH; - hints.functions |= MWM_FUNC_MAXIMIZE | MWM_FUNC_RESIZE; - } - if (style & Style::Close) - { - hints.decorations |= 0; - hints.functions |= MWM_FUNC_CLOSE; - } - - const unsigned char* ptr = reinterpret_cast<const unsigned char*>(&hints); - XChangeProperty(m_display, m_window, WMHintsAtom, WMHintsAtom, 32, PropModeReplace, ptr, 5); - } - - // This is a hack to force some windows managers to disable resizing - if (!(style & Style::Resize)) - { - m_useSizeHints = true; - XSizeHints* sizeHints = XAllocSizeHints(); - sizeHints->flags = PMinSize | PMaxSize; - sizeHints->min_width = sizeHints->max_width = width; - sizeHints->min_height = sizeHints->max_height = height; - XSetWMNormalHints(m_display, m_window, sizeHints); - XFree(sizeHints); - } + m_useSizeHints = true; + sizeHints.flags |= ((1 << 4) | (1 << 5)); + sizeHints.min_width = width; + sizeHints.max_width = width; + sizeHints.min_height = height; + sizeHints.max_height = height; } + // Set the WM hints of the normal state + setWMSizeHints(sizeHints); + // Set the window's WM class (this can be used by window managers) - char windowClass[512]; - findExecutableName(windowClass, sizeof(windowClass)); - XClassHint* classHint = XAllocClassHint(); - classHint->res_name = windowClass; - classHint->res_class = windowClass; - XSetClassHint(m_display, m_window, classHint); - XFree(classHint); + // The WM_CLASS property actually consists of 2 parts, + // the instance name and the class name both of which should be + // null terminated strings. + // The instance name should be something unique to this invokation + // of the application but is rarely if ever used these days. + // For simplicity, we retrieve it via the base executable name. + // The class name identifies a class of windows that + // "are of the same type". We simply use the initial window name as + // the class name. + std::string windowClass = findExecutableName(); + windowClass += '\0'; // Important to separate instance from class + windowClass += title.toAnsiString(); + + // We add 1 to the size of the string to include the null at the end + if (!changeWindowProperty(XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, windowClass.size() + 1, windowClass.c_str())) + sf::err() << "Failed to set WM_CLASS property" << std::endl; + + // Set the window's name + setTitle(title); // Do some common initializations initialize(); - // In fullscreen mode, we must grab keyboard and mouse inputs - if (fullscreen) + // Set fullscreen video mode and switch to fullscreen if necessary + if (m_fullscreen) { - XGrabPointer(m_display, m_window, true, 0, GrabModeAsync, GrabModeAsync, m_window, None, CurrentTime); - XGrabKeyboard(m_display, m_window, true, GrabModeAsync, GrabModeAsync, CurrentTime); + setPosition(Vector2i(0, 0)); + setVideoMode(mode); + switchToFullscreen(); } } @@ -286,17 +698,16 @@ WindowImplX11::~WindowImplX11() // Destroy the cursor if (m_hiddenCursor) - XFreeCursor(m_display, m_hiddenCursor); + xcb_free_cursor(m_connection, m_hiddenCursor); // Destroy the input context if (m_inputContext) XDestroyIC(m_inputContext); - // Destroy the window if (m_window && !m_isExternal) { - XDestroyWindow(m_display, m_window); - XFlush(m_display); + xcb_destroy_window(m_connection, m_window); + xcb_flush(m_connection); } // Close the input method @@ -307,6 +718,7 @@ WindowImplX11::~WindowImplX11() CloseDisplay(m_display); // Remove this window from the global list of windows (required for focus request) + Lock lock(allWindowsMutex); allWindows.erase(std::find(allWindows.begin(), allWindows.end(), this)); } @@ -321,10 +733,105 @@ WindowHandle WindowImplX11::getSystemHandle() const //////////////////////////////////////////////////////////// void WindowImplX11::processEvents() { - XEvent event; - while (XCheckIfEvent(m_display, &event, &checkEvent, reinterpret_cast<XPointer>(m_window))) + // Key repeat workaround: If key repeat is enabled, XCB will spawn two + // events for each repeat interval: key release and key press. Both have + // the same timestamp and key code. We are holding back the release event + // to check for the matching key press event and if so, discard the release + // event. + + xcb_generic_event_t* event = NULL; + xcb_key_release_event_t* lastKeyReleaseEvent = NULL; + uint8_t eventType = 0; + + if (!m_xcbEvents.empty()) { - processEvent(event); + event = m_xcbEvents.front(); + m_xcbEvents.pop_front(); + } + else + { + event = xcb_poll_for_event(m_connection); + } + + while (event) + { + eventType = event->response_type & ~0x80; + + // Key was pressed and one has been released prior to that. + if (eventType == XCB_KEY_PRESS && lastKeyReleaseEvent) + { + // If the key press event matches the held back key release event, + // then we have a key repeat and discard the held back release + // event. + if (lastKeyReleaseEvent->time == reinterpret_cast<xcb_key_press_event_t*>(event)->time && + lastKeyReleaseEvent->detail == reinterpret_cast<xcb_key_press_event_t*>(event)->detail) + { + free(lastKeyReleaseEvent); + lastKeyReleaseEvent = NULL; + + // If key repeat is disabled, discard the matching key press event as well + if (!m_keyRepeat) + { + free(event); + + if (!m_xcbEvents.empty()) + { + event = m_xcbEvents.front(); + m_xcbEvents.pop_front(); + } + else + { + event = xcb_poll_for_event(m_connection); + } + + continue; + } + } + } + + // If there's still a key release event held back, process it now. + if (lastKeyReleaseEvent) + { + if (processEvent(reinterpret_cast<xcb_generic_event_t*>(lastKeyReleaseEvent))) + free(lastKeyReleaseEvent); + + lastKeyReleaseEvent = NULL; + } + + if (eventType == XCB_KEY_RELEASE) + { + // Check if we're in charge of the key release + if (!passEvent(event, reinterpret_cast<xcb_key_release_event_t*>(event)->event)) + { + // Remember this key release event. + lastKeyReleaseEvent = reinterpret_cast<xcb_key_release_event_t*>(event); + event = NULL; // For safety reasons. + } + } + else + { + if (processEvent(event)) + free(event); + } + + if (!m_xcbEvents.empty()) + { + event = m_xcbEvents.front(); + m_xcbEvents.pop_front(); + } + else + { + event = xcb_poll_for_event(m_connection); + } + } + + // Process any held back release event. + if (lastKeyReleaseEvent) + { + if (processEvent(reinterpret_cast<xcb_generic_event_t*>(lastKeyReleaseEvent))) + free(lastKeyReleaseEvent); + + lastKeyReleaseEvent = NULL; } } @@ -332,71 +839,146 @@ void WindowImplX11::processEvents() //////////////////////////////////////////////////////////// Vector2i WindowImplX11::getPosition() const { - ::Window root, child; - int localX, localY, x, y; - unsigned int width, height, border, depth; + ::Window topLevelWindow = m_window; + ::Window nextWindow = topLevelWindow; + + ScopedXcbPtr<xcb_generic_error_t> error(NULL); + + // Get "top level" window, i.e. the window with the root window as its parent. + while (nextWindow != m_screen->root) + { + topLevelWindow = nextWindow; + + ScopedXcbPtr<xcb_query_tree_reply_t> treeReply(xcb_query_tree_reply( + m_connection, + xcb_query_tree( + m_connection, + topLevelWindow + ), + &error + )); + + if (error) + { + err() << "Failed to get window position (query_tree)" << std::endl; + return Vector2i(0, 0); + } + + nextWindow = treeReply->parent; + } + + ScopedXcbPtr<xcb_get_geometry_reply_t> geometryReply(xcb_get_geometry_reply( + m_connection, + xcb_get_geometry( + m_connection, + topLevelWindow + ), + &error + )); - XGetGeometry(m_display, m_window, &root, &localX, &localY, &width, &height, &border, &depth); - XTranslateCoordinates(m_display, m_window, root, localX, localY, &x, &y, &child); + if (error) + { + err() << "Failed to get window position (get_geometry)" << std::endl; + return Vector2i(0, 0); + } - return Vector2i(x, y); + return Vector2i(geometryReply->x, geometryReply->y); } //////////////////////////////////////////////////////////// void WindowImplX11::setPosition(const Vector2i& position) { - XMoveWindow(m_display, m_window, position.x, position.y); - XFlush(m_display); + uint32_t values[] = {static_cast<uint32_t>(position.x), static_cast<uint32_t>(position.y)}; + xcb_configure_window(m_connection, m_window, + XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, + values); + xcb_flush(m_connection); } //////////////////////////////////////////////////////////// Vector2u WindowImplX11::getSize() const { - XWindowAttributes attributes; - XGetWindowAttributes(m_display, m_window, &attributes); - return Vector2u(attributes.width, attributes.height); + ScopedXcbPtr<xcb_get_geometry_reply_t> reply(xcb_get_geometry_reply(m_connection, xcb_get_geometry(m_connection, m_window), NULL)); + + return Vector2u(reply->width, reply->height); } //////////////////////////////////////////////////////////// void WindowImplX11::setSize(const Vector2u& size) { - // If we used size hint to fix the size of the window, we must update them - if (m_useSizeHints) - { - XSizeHints* sizeHints = XAllocSizeHints(); - sizeHints->flags = PMinSize | PMaxSize; - sizeHints->min_width = sizeHints->max_width = size.x; - sizeHints->min_height = sizeHints->max_height = size.y; - XSetWMNormalHints(m_display, m_window, sizeHints); - XFree(sizeHints); + // If resizing is disable for the window we have to update the size hints (required by some window managers). + if( m_useSizeHints ) { + WMSizeHints sizeHints; + std::memset(&sizeHints, 0, sizeof(sizeHints)); + + sizeHints.flags |= (1 << 4 | 1 << 5); + sizeHints.min_width = size.x; + sizeHints.max_width = size.x; + sizeHints.min_height = size.y; + sizeHints.max_height = size.y; + + setWMSizeHints(sizeHints); } - XResizeWindow(m_display, m_window, size.x, size.y); - XFlush(m_display); + uint32_t values[] = {size.x, size.y}; + + ScopedXcbPtr<xcb_generic_error_t> configureWindowError(xcb_request_check( + m_connection, + xcb_configure_window( + m_connection, + m_window, + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, + values + ) + )); + + if (configureWindowError) + err() << "Failed to set window size" << std::endl; + + xcb_flush(m_connection); } //////////////////////////////////////////////////////////// void WindowImplX11::setTitle(const String& title) { - // Bare X11 has no Unicode window title support. - // There is however an option to tell the window manager your Unicode title via hints. + // XCB takes UTF-8-encoded strings. + xcb_atom_t utf8StringType = getAtom("UTF8_STRING"); + + if (!utf8StringType) + utf8StringType = XCB_ATOM_STRING; + + std::string utf8String; + Utf<32>::toUtf8(title.begin(), title.end(), std::back_inserter(utf8String)); + + if (!changeWindowProperty(XCB_ATOM_WM_NAME, utf8StringType, 8, utf8String.length(), utf8String.c_str())) + err() << "Failed to set window title" << std::endl; - // Convert to UTF-8 encoding. - std::basic_string<Uint8> utf8Title; - Utf32::toUtf8(title.begin(), title.end(), std::back_inserter(utf8Title)); + if (!changeWindowProperty(XCB_ATOM_WM_ICON_NAME, utf8StringType, 8, utf8String.length(), utf8String.c_str())) + err() << "Failed to set WM_ICON_NAME property" << std::endl; - // Set the _NET_WM_NAME atom, which specifies a UTF-8 encoded window title. - Atom wmName = XInternAtom(m_display, "_NET_WM_NAME", False); - Atom useUtf8 = XInternAtom(m_display, "UTF8_STRING", False); - XChangeProperty(m_display, m_window, wmName, useUtf8, 8, - PropModeReplace, utf8Title.c_str(), utf8Title.size()); + if (ewmhSupported()) + { + xcb_atom_t netWmName = getAtom("_NET_WM_NAME", true); + xcb_atom_t netWmIconName = getAtom("_NET_WM_ICON_NAME", true); + + if (utf8StringType && netWmName) + { + if (!changeWindowProperty(netWmName, utf8StringType, 8, utf8String.length(), utf8String.c_str())) + err() << "Failed to set _NET_WM_NAME property" << std::endl; + } + + if (utf8StringType && netWmName) + { + if (!changeWindowProperty(netWmIconName, utf8StringType, 8, utf8String.length(), utf8String.c_str())) + err() << "Failed to set _NET_WM_ICON_NAME property" << std::endl; + } + } - // Set the non-Unicode title as a fallback for window managers who don't support _NET_WM_NAME. - XStoreName(m_display, m_window, title.toAnsiString().c_str()); + xcb_flush(m_connection); } @@ -404,8 +986,7 @@ void WindowImplX11::setTitle(const String& title) void WindowImplX11::setIcon(unsigned int width, unsigned int height, const Uint8* pixels) { // X11 wants BGRA pixels: swap red and blue channels - // Note: this memory will be freed by XDestroyImage - Uint8* iconPixels = static_cast<Uint8*>(std::malloc(width * height * 4)); + Uint8 iconPixels[width * height * 4]; for (std::size_t i = 0; i < width * height; ++i) { iconPixels[i * 4 + 0] = pixels[i * 4 + 2]; @@ -415,20 +996,85 @@ void WindowImplX11::setIcon(unsigned int width, unsigned int height, const Uint8 } // Create the icon pixmap - Visual* defVisual = DefaultVisual(m_display, m_screen); - unsigned int defDepth = DefaultDepth(m_display, m_screen); - XImage* iconImage = XCreateImage(m_display, defVisual, defDepth, ZPixmap, 0, (char*)iconPixels, width, height, 32, 0); - if (!iconImage) + xcb_pixmap_t iconPixmap = xcb_generate_id(m_connection); + + ScopedXcbPtr<xcb_generic_error_t> createPixmapError(xcb_request_check( + m_connection, + xcb_create_pixmap_checked( + m_connection, + m_screen->root_depth, + iconPixmap, + m_screen->root, + width, + height + ) + )); + + if (createPixmapError) { - err() << "Failed to set the window's icon" << std::endl; + err() << "Failed to set the window's icon (create_pixmap): "; + err() << "Error code " << static_cast<int>(createPixmapError->error_code) << std::endl; + return; + } + + xcb_gcontext_t iconGC = xcb_generate_id(m_connection); + + ScopedXcbPtr<xcb_generic_error_t> createGcError(xcb_request_check( + m_connection, + xcb_create_gc( + m_connection, + iconGC, + iconPixmap, + 0, + NULL + ) + )); + + if (createGcError) + { + err() << "Failed to set the window's icon (create_gc): "; + err() << "Error code " << static_cast<int>(createGcError->error_code) << std::endl; + return; + } + + ScopedXcbPtr<xcb_generic_error_t> putImageError(xcb_request_check( + m_connection, + xcb_put_image_checked( + m_connection, + XCB_IMAGE_FORMAT_Z_PIXMAP, + iconPixmap, + iconGC, + width, + height, + 0, + 0, + 0, + m_screen->root_depth, + sizeof(iconPixels), + iconPixels + ) + )); + + ScopedXcbPtr<xcb_generic_error_t> freeGcError(xcb_request_check( + m_connection, + xcb_free_gc( + m_connection, + iconGC + ) + )); + + if (freeGcError) + { + err() << "Failed to free icon GC: "; + err() << "Error code " << static_cast<int>(freeGcError->error_code) << std::endl; + } + + if (putImageError) + { + err() << "Failed to set the window's icon (put_image): "; + err() << "Error code " << static_cast<int>(putImageError->error_code) << std::endl; return; } - Pixmap iconPixmap = XCreatePixmap(m_display, RootWindow(m_display, m_screen), width, height, defDepth); - XGCValues values; - GC iconGC = XCreateGC(m_display, iconPixmap, 0, &values); - XPutImage(m_display, iconPixmap, iconGC, iconImage, 0, 0, 0, 0, width, height); - XFreeGC(m_display, iconGC); - XDestroyImage(iconImage); // Create the mask pixmap (must have 1 bit depth) std::size_t pitch = (width + 7) / 8; @@ -447,17 +1093,43 @@ void WindowImplX11::setIcon(unsigned int width, unsigned int height, const Uint8 } } } - Pixmap maskPixmap = XCreatePixmapFromBitmapData(m_display, m_window, (char*)&maskPixels[0], width, height, 1, 0, 1); + + xcb_pixmap_t maskPixmap = xcb_create_pixmap_from_bitmap_data( + m_connection, + m_window, + reinterpret_cast<uint8_t*>(&maskPixels[0]), + width, + height, + 1, + 0, + 1, + NULL + ); // Send our new icon to the window through the WMHints - XWMHints* hints = XAllocWMHints(); - hints->flags = IconPixmapHint | IconMaskHint; - hints->icon_pixmap = iconPixmap; - hints->icon_mask = maskPixmap; - XSetWMHints(m_display, m_window, hints); - XFree(hints); - - XFlush(m_display); + WMHints hints; + std::memset(&hints, 0, sizeof(hints)); + hints.flags |= ((1 << 2) | (1 << 5)); + hints.icon_pixmap = iconPixmap; + hints.icon_mask = maskPixmap; + + setWMHints(hints); + + xcb_flush(m_connection); + + ScopedXcbPtr<xcb_generic_error_t> freePixmapError(xcb_request_check( + m_connection, + xcb_free_pixmap_checked( + m_connection, + iconPixmap + ) + )); + + if (freePixmapError) + { + err() << "Failed to free icon pixmap: "; + err() << "Error code " << static_cast<int>(freePixmapError->error_code) << std::endl; + } } @@ -465,19 +1137,55 @@ void WindowImplX11::setIcon(unsigned int width, unsigned int height, const Uint8 void WindowImplX11::setVisible(bool visible) { if (visible) - XMapWindow(m_display, m_window); + { + ScopedXcbPtr<xcb_generic_error_t> error(xcb_request_check( + m_connection, + xcb_map_window( + m_connection, + m_window + ) + )); + + if (error) + err() << "Failed to change window visibility" << std::endl; + } else - XUnmapWindow(m_display, m_window); + { + ScopedXcbPtr<xcb_generic_error_t> error(xcb_request_check( + m_connection, + xcb_unmap_window( + m_connection, + m_window + ) + )); + + if (error) + err() << "Failed to change window visibility" << std::endl; + } - XFlush(m_display); + xcb_flush(m_connection); } //////////////////////////////////////////////////////////// void WindowImplX11::setMouseCursorVisible(bool visible) { - XDefineCursor(m_display, m_window, visible ? None : m_hiddenCursor); - XFlush(m_display); + const uint32_t values = visible ? XCB_NONE : m_hiddenCursor; + + ScopedXcbPtr<xcb_generic_error_t> error(xcb_request_check( + m_connection, + xcb_change_window_attributes( + m_connection, + m_window, + XCB_CW_CURSOR, + &values + ) + )); + + if (error) + err() << "Failed to change mouse cursor visibility" << std::endl; + + xcb_flush(m_connection); } @@ -495,45 +1203,73 @@ void WindowImplX11::requestFocus() // Check the global list of windows to find out whether an SFML window has the focus // Note: can't handle console and other non-SFML windows belonging to the application. bool sfmlWindowFocused = false; - for (std::vector<WindowImplX11*>::iterator itr = allWindows.begin(); itr != allWindows.end(); ++itr) + { - if ((*itr)->hasFocus()) + Lock lock(allWindowsMutex); + for (std::vector<WindowImplX11*>::iterator itr = allWindows.begin(); itr != allWindows.end(); ++itr) { - sfmlWindowFocused = true; - break; + if ((*itr)->hasFocus()) + { + sfmlWindowFocused = true; + break; + } } } - + + ScopedXcbPtr<xcb_generic_error_t> error(NULL); + // Check if window is viewable (not on other desktop, ...) // TODO: Check also if minimized - XWindowAttributes attributes; - if (XGetWindowAttributes(m_display, m_window, &attributes) == 0) + ScopedXcbPtr<xcb_get_window_attributes_reply_t> attributes(xcb_get_window_attributes_reply( + m_connection, + xcb_get_window_attributes( + m_connection, + m_window + ), + &error + )); + + if (error || !attributes) { - sf::err() << "Failed to check if window is viewable while requesting focus" << std::endl; + err() << "Failed to check if window is viewable while requesting focus" << std::endl; return; // error getting attribute } - bool windowViewable = (attributes.map_state == IsViewable); - + bool windowViewable = (attributes->map_state == XCB_MAP_STATE_VIEWABLE); + if (sfmlWindowFocused && windowViewable) { // Another SFML window of this application has the focus and the current window is viewable: // steal focus (i.e. bring window to the front and give it input focus) - XRaiseWindow(m_display, m_window); - XSetInputFocus(m_display, m_window, RevertToPointerRoot, CurrentTime); + grabFocus(); } else { - // Otherwise: display urgency hint (flashing application logo) - // Ensure WM hints exist, allocate if necessary - XWMHints* hints = XGetWMHints(m_display, m_window); - if (hints == NULL) - hints = XAllocWMHints(); - - // Add urgency (notification) flag to hints - hints->flags |= XUrgencyHint; - XSetWMHints(m_display, m_window, hints); - XFree(hints); + // Get current WM hints. + ScopedXcbPtr<xcb_get_property_reply_t> hintsReply(xcb_get_property_reply( + m_connection, + xcb_get_property(m_connection, 0, m_window, XCB_ATOM_WM_HINTS, XCB_ATOM_WM_HINTS, 0, 9), + &error + )); + + if (error || !hintsReply) + { + err() << "Failed to get WM hints while requesting focus" << std::endl; + return; + } + + WMHints* hints = reinterpret_cast<WMHints*>(xcb_get_property_value(hintsReply.get())); + + if (!hints) + { + err() << "Failed to get WM hints while requesting focus" << std::endl; + return; + } + + // Even if no hints were returned, we can simply set the proper flags we need and go on. This is + // different from Xlib where XAllocWMHints() has to be called. + hints->flags |= (1 << 8); + setWMHints(*hints); } } @@ -541,101 +1277,498 @@ void WindowImplX11::requestFocus() //////////////////////////////////////////////////////////// bool WindowImplX11::hasFocus() const { - ::Window focusedWindow = 0; - int revertToReturn = 0; - XGetInputFocus(m_display, &focusedWindow, &revertToReturn); + ScopedXcbPtr<xcb_generic_error_t> error(NULL); + + ScopedXcbPtr<xcb_get_input_focus_reply_t> reply(xcb_get_input_focus_reply( + m_connection, + xcb_get_input_focus_unchecked( + m_connection + ), + &error + )); - return m_window == focusedWindow; + if (error) + err() << "Failed to check if window has focus" << std::endl; + + return (reply->focus == m_window); } //////////////////////////////////////////////////////////// -void WindowImplX11::switchToFullscreen(const VideoMode& mode) +void WindowImplX11::grabFocus() { - // Check if the XRandR extension is present - int version; - if (XQueryExtension(m_display, "RANDR", &version, &version, &version)) + xcb_atom_t netActiveWindow = XCB_ATOM_NONE; + + if (ewmhSupported()) + netActiveWindow = getAtom("_NET_ACTIVE_WINDOW"); + + if (netActiveWindow) + { + xcb_client_message_event_t event; + std::memset(&event, 0, sizeof(event)); + + event.response_type = XCB_CLIENT_MESSAGE; + event.window = m_window; + event.format = 32; + event.sequence = 0; + event.type = netActiveWindow; + event.data.data32[0] = 1; // Normal application + event.data.data32[1] = XCB_CURRENT_TIME; + event.data.data32[2] = 0; // We don't know the currently active window + + ScopedXcbPtr<xcb_generic_error_t> activeWindowError(xcb_request_check( + m_connection, + xcb_send_event_checked( + m_connection, + 0, + XCBDefaultRootWindow(m_connection), + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, + reinterpret_cast<char*>(&event) + ) + )); + + if (activeWindowError) + err() << "Setting fullscreen failed, could not send \"_NET_ACTIVE_WINDOW\" event" << std::endl; + } + else { - // Get the current configuration - XRRScreenConfiguration* config = XRRGetScreenInfo(m_display, RootWindow(m_display, m_screen)); - if (config) + ScopedXcbPtr<xcb_generic_error_t> setInputFocusError(xcb_request_check( + m_connection, + xcb_set_input_focus( + m_connection, + XCB_INPUT_FOCUS_POINTER_ROOT, + m_window, + XCB_CURRENT_TIME + ) + )); + + if (setInputFocusError) { - // Get the current rotation - Rotation currentRotation; - m_oldVideoMode = XRRConfigCurrentConfiguration(config, ¤tRotation); - - // Get the available screen sizes - int nbSizes; - XRRScreenSize* sizes = XRRConfigSizes(config, &nbSizes); - if (sizes && (nbSizes > 0)) - { - // Search a matching size - for (int i = 0; i < nbSizes; ++i) - { - if ((sizes[i].width == static_cast<int>(mode.width)) && (sizes[i].height == static_cast<int>(mode.height))) - { - // Switch to fullscreen mode - XRRSetScreenConfig(m_display, config, RootWindow(m_display, m_screen), i, currentRotation, CurrentTime); + err() << "Failed to change active window (set_input_focus)" << std::endl; + return; + } - // Set "this" as the current fullscreen window - fullscreenWindow = this; - break; - } - } - } + const uint32_t values[] = {XCB_STACK_MODE_ABOVE}; + + ScopedXcbPtr<xcb_generic_error_t> configureWindowError(xcb_request_check( + m_connection, + xcb_configure_window( + m_connection, + m_window, + XCB_CONFIG_WINDOW_STACK_MODE, + values + ) + )); + + if (configureWindowError) + err() << "Failed to change active window (configure_window)" << std::endl; + } +} + + +//////////////////////////////////////////////////////////// +void WindowImplX11::setVideoMode(const VideoMode& mode) +{ + // Skip mode switching if the new mode is equal to the desktop mode + if (mode == VideoMode::getDesktopMode()) + return; + + ScopedXcbPtr<xcb_generic_error_t> error(NULL); + + // Check if the RandR extension is present + const xcb_query_extension_reply_t* randrExt = xcb_get_extension_data(m_connection, &xcb_randr_id); - // Free the configuration instance - XRRFreeScreenConfigInfo(config); + if (!randrExt || !randrExt->present) + { + // RandR extension is not supported: we cannot use fullscreen mode + err() << "Fullscreen is not supported, switching to window mode" << std::endl; + return; + } + + // Load RandR and check its version + ScopedXcbPtr<xcb_randr_query_version_reply_t> randrVersion(xcb_randr_query_version_reply( + m_connection, + xcb_randr_query_version( + m_connection, + 1, + 1 + ), + &error + )); + + if (error) + { + err() << "Failed to load RandR, switching to window mode" << std::endl; + return; + } + + // Get the current configuration + ScopedXcbPtr<xcb_randr_get_screen_info_reply_t> config(xcb_randr_get_screen_info_reply( + m_connection, + xcb_randr_get_screen_info( + m_connection, + m_screen->root + ), + &error + )); + + if (error || !config) + { + // Failed to get the screen configuration + err() << "Failed to get the current screen configuration for fullscreen mode, switching to window mode" << std::endl; + return; + } + + // Save the current video mode before we switch to fullscreen + m_oldVideoMode = *config.get(); + + // Get the available screen sizes + xcb_randr_screen_size_t* sizes = xcb_randr_get_screen_info_sizes(config.get()); + + if (!sizes || !config->nSizes) + { + err() << "Failed to get the fullscreen sizes, switching to window mode" << std::endl; + return; + } + + // Search for a matching size + for (int i = 0; i < config->nSizes; ++i) + { + if (config->rotation == XCB_RANDR_ROTATION_ROTATE_90 || + config->rotation == XCB_RANDR_ROTATION_ROTATE_270) + std::swap(sizes[i].height, sizes[i].width); + + if ((sizes[i].width == static_cast<int>(mode.width)) && + (sizes[i].height == static_cast<int>(mode.height))) + { + // Switch to fullscreen mode + ScopedXcbPtr<xcb_randr_set_screen_config_reply_t> setScreenConfig(xcb_randr_set_screen_config_reply( + m_connection, + xcb_randr_set_screen_config( + m_connection, + config->root, + XCB_CURRENT_TIME, + config->config_timestamp, + i, + config->rotation, + config->rate + ), + &error + )); + + if (error) + err() << "Failed to set new screen configuration" << std::endl; + + // Set "this" as the current fullscreen window + fullscreenWindow = this; + return; } - else + } + + err() << "Failed to find matching fullscreen size, switching to window mode" << std::endl; +} + + +//////////////////////////////////////////////////////////// +void WindowImplX11::resetVideoMode() +{ + if (fullscreenWindow == this) + { + // Get current screen info + ScopedXcbPtr<xcb_generic_error_t> error(NULL); + + // Reset the video mode + ScopedXcbPtr<xcb_randr_set_screen_config_reply_t> setScreenConfig(xcb_randr_set_screen_config_reply( + m_connection, + xcb_randr_set_screen_config( + m_connection, + m_oldVideoMode.root, + XCB_CURRENT_TIME, + m_oldVideoMode.config_timestamp, + m_oldVideoMode.sizeID, + m_oldVideoMode.rotation, + m_oldVideoMode.rate + ), + &error + )); + + if (error) + err() << "Failed to reset old screen configuration" << std::endl; + + // Reset the fullscreen window + fullscreenWindow = NULL; + } +} + + +//////////////////////////////////////////////////////////// +void WindowImplX11::switchToFullscreen() +{ + grabFocus(); + + if (ewmhSupported()) + { + xcb_atom_t netWmBypassCompositor = getAtom("_NET_WM_BYPASS_COMPOSITOR"); + + if (netWmBypassCompositor) + { + static const Uint32 bypassCompositor = 1; + + // Not being able to bypass the compositor is not a fatal error + if (!changeWindowProperty(netWmBypassCompositor, XCB_ATOM_CARDINAL, 32, 1, &bypassCompositor)) + err() << "xcb_change_property failed, unable to set _NET_WM_BYPASS_COMPOSITOR" << std::endl; + } + + xcb_atom_t netWmState = getAtom("_NET_WM_STATE", true); + xcb_atom_t netWmStateFullscreen = getAtom("_NET_WM_STATE_FULLSCREEN", true); + + if (!netWmState || !netWmStateFullscreen) { - // Failed to get the screen configuration - err() << "Failed to get the current screen configuration for fullscreen mode, switching to window mode" << std::endl; + err() << "Setting fullscreen failed. Could not get required atoms" << std::endl; + return; } + + xcb_client_message_event_t event; + std::memset(&event, 0, sizeof(event)); + + event.response_type = XCB_CLIENT_MESSAGE; + event.window = m_window; + event.format = 32; + event.sequence = 0; + event.type = netWmState; + event.data.data32[0] = 1; // _NET_WM_STATE_ADD + event.data.data32[1] = netWmStateFullscreen; + event.data.data32[2] = 0; // No second property + event.data.data32[3] = 1; // Normal window + + ScopedXcbPtr<xcb_generic_error_t> wmStateError(xcb_request_check( + m_connection, + xcb_send_event_checked( + m_connection, + 0, + XCBDefaultRootWindow(m_connection), + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, + reinterpret_cast<char*>(&event) + ) + )); + + if (wmStateError) + err() << "Setting fullscreen failed. Could not send \"_NET_WM_STATE\" event" << std::endl; + } +} + + +//////////////////////////////////////////////////////////// +void WindowImplX11::setProtocols() +{ + xcb_atom_t wmProtocols = getAtom("WM_PROTOCOLS"); + xcb_atom_t wmDeleteWindow = getAtom("WM_DELETE_WINDOW"); + + if (!wmProtocols) + { + err() << "Failed to request WM_PROTOCOLS atom." << std::endl; + return; + } + + std::vector<xcb_atom_t> atoms; + + if (wmDeleteWindow) + { + atoms.push_back(wmDeleteWindow); } else { - // XRandr extension is not supported: we cannot use fullscreen mode - err() << "Fullscreen is not supported, switching to window mode" << std::endl; + err() << "Failed to request WM_DELETE_WINDOW atom." << std::endl; + } + + xcb_atom_t netWmPing = XCB_ATOM_NONE; + xcb_atom_t netWmPid = XCB_ATOM_NONE; + + if (ewmhSupported()) + { + netWmPing = getAtom("_NET_WM_PING", true); + netWmPid = getAtom("_NET_WM_PID", true); + } + + ScopedXcbPtr<xcb_generic_error_t> error(NULL); + + if (netWmPing && netWmPid) + { + uint32_t pid = getpid(); + + if (changeWindowProperty(netWmPid, XCB_ATOM_CARDINAL, 32, 1, &pid)) + atoms.push_back(netWmPing); + } + + if (!atoms.empty()) + { + if (!changeWindowProperty(wmProtocols, XCB_ATOM_ATOM, 32, atoms.size(), &atoms[0])) + err() << "Failed to set window protocols" << std::endl; + } + else + { + err() << "Didn't set any window protocols" << std::endl; } } //////////////////////////////////////////////////////////// -void WindowImplX11::initialize() +void WindowImplX11::setMotifHints(unsigned long style) { - // Get the atom defining the close event - m_atomClose = XInternAtom(m_display, "WM_DELETE_WINDOW", false); - XSetWMProtocols(m_display, m_window, &m_atomClose, 1); + ScopedXcbPtr<xcb_generic_error_t> error(NULL); + + static const std::string MOTIF_WM_HINTS = "_MOTIF_WM_HINTS"; + ScopedXcbPtr<xcb_intern_atom_reply_t> hintsAtomReply(xcb_intern_atom_reply( + m_connection, + xcb_intern_atom( + m_connection, + 0, + MOTIF_WM_HINTS.size(), + MOTIF_WM_HINTS.c_str() + ), + &error + )); + + if (!error && hintsAtomReply) + { + static const unsigned long MWM_HINTS_FUNCTIONS = 1 << 0; + static const unsigned long MWM_HINTS_DECORATIONS = 1 << 1; + + //static const unsigned long MWM_DECOR_ALL = 1 << 0; + static const unsigned long MWM_DECOR_BORDER = 1 << 1; + static const unsigned long MWM_DECOR_RESIZEH = 1 << 2; + static const unsigned long MWM_DECOR_TITLE = 1 << 3; + static const unsigned long MWM_DECOR_MENU = 1 << 4; + static const unsigned long MWM_DECOR_MINIMIZE = 1 << 5; + static const unsigned long MWM_DECOR_MAXIMIZE = 1 << 6; + + //static const unsigned long MWM_FUNC_ALL = 1 << 0; + static const unsigned long MWM_FUNC_RESIZE = 1 << 1; + static const unsigned long MWM_FUNC_MOVE = 1 << 2; + static const unsigned long MWM_FUNC_MINIMIZE = 1 << 3; + static const unsigned long MWM_FUNC_MAXIMIZE = 1 << 4; + static const unsigned long MWM_FUNC_CLOSE = 1 << 5; + + struct MotifWMHints + { + uint32_t flags; + uint32_t functions; + uint32_t decorations; + int32_t inputMode; + uint32_t state; + }; + + MotifWMHints hints; + hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS; + hints.decorations = 0; + hints.functions = 0; + hints.inputMode = 0; + hints.state = 0; + + if (style & Style::Titlebar) + { + hints.decorations |= MWM_DECOR_BORDER | MWM_DECOR_TITLE | MWM_DECOR_MINIMIZE | MWM_DECOR_MENU; + hints.functions |= MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE; + } + if (style & Style::Resize) + { + hints.decorations |= MWM_DECOR_MAXIMIZE | MWM_DECOR_RESIZEH; + hints.functions |= MWM_FUNC_MAXIMIZE | MWM_FUNC_RESIZE; + } + if (style & Style::Close) + { + hints.decorations |= 0; + hints.functions |= MWM_FUNC_CLOSE; + } + + if (!changeWindowProperty(hintsAtomReply->atom, hintsAtomReply->atom, 32, 5, &hints)) + err() << "xcb_change_property failed, could not set window hints" << std::endl; + } + else + { + err() << "Failed to request _MOTIF_WM_HINTS atom." << std::endl; + } +} + +//////////////////////////////////////////////////////////// +void WindowImplX11::setWMHints(const WMHints& hints) +{ + if (!changeWindowProperty(XCB_ATOM_WM_HINTS, XCB_ATOM_WM_HINTS, 32, sizeof(hints) / 4, &hints)) + sf::err() << "Failed to set WM_HINTS property" << std::endl; +} + + +//////////////////////////////////////////////////////////// +void WindowImplX11::setWMSizeHints(const WMSizeHints& hints) +{ + if (!changeWindowProperty(XCB_ATOM_WM_NORMAL_HINTS, XCB_ATOM_WM_SIZE_HINTS, 32, sizeof(hints) / 4, &hints)) + sf::err() << "Failed to set XCB_ATOM_WM_NORMAL_HINTS property" << std::endl; +} + + +//////////////////////////////////////////////////////////// +bool WindowImplX11::changeWindowProperty(xcb_atom_t property, xcb_atom_t type, uint8_t format, uint32_t length, const void* data) +{ + ScopedXcbPtr<xcb_generic_error_t> error(xcb_request_check( + m_connection, + xcb_change_property_checked( + m_connection, + XCB_PROP_MODE_REPLACE, + m_window, + property, + type, + format, + length, + data + ) + )); + + return !error; +} + + +//////////////////////////////////////////////////////////// +void WindowImplX11::initialize() +{ // Create the input context m_inputMethod = XOpenIM(m_display, NULL, NULL, NULL); + if (m_inputMethod) { - m_inputContext = XCreateIC(m_inputMethod, - XNClientWindow, m_window, - XNFocusWindow, m_window, - XNInputStyle, XIMPreeditNothing | XIMStatusNothing, - (void*)NULL); + m_inputContext = XCreateIC( + m_inputMethod, + XNClientWindow, + m_window, + XNFocusWindow, + m_window, + XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + reinterpret_cast<void*>(NULL) + ); } else { m_inputContext = NULL; } + if (!m_inputContext) err() << "Failed to create input context for window -- TextEntered event won't be able to return unicode" << std::endl; // Show the window - XMapWindow(m_display, m_window); - XFlush(m_display); + setVisible(true); + + // Raise the window and grab input focus + grabFocus(); // Create the hidden cursor createHiddenCursor(); // Flush the commands queue - XFlush(m_display); - + xcb_flush(m_connection); + // Add this window to the global list of windows (required for focus request) + Lock lock(allWindowsMutex); allWindows.push_back(this); } @@ -643,20 +1776,58 @@ void WindowImplX11::initialize() //////////////////////////////////////////////////////////// void WindowImplX11::createHiddenCursor() { + xcb_pixmap_t cursorPixmap = xcb_generate_id(m_connection); + // Create the cursor's pixmap (1x1 pixels) - Pixmap cursorPixmap = XCreatePixmap(m_display, m_window, 1, 1, 1); - GC graphicsContext = XCreateGC(m_display, cursorPixmap, 0, NULL); - XDrawPoint(m_display, cursorPixmap, graphicsContext, 0, 0); - XFreeGC(m_display, graphicsContext); + ScopedXcbPtr<xcb_generic_error_t> createPixmapError(xcb_request_check( + m_connection, + xcb_create_pixmap( + m_connection, + 1, + cursorPixmap, + m_window, + 1, + 1 + ) + )); + + if (createPixmapError) + { + err() << "Failed to create pixmap for hidden cursor" << std::endl; + return; + } + + m_hiddenCursor = xcb_generate_id(m_connection); // Create the cursor, using the pixmap as both the shape and the mask of the cursor - XColor color; - color.flags = DoRed | DoGreen | DoBlue; - color.red = color.blue = color.green = 0; - m_hiddenCursor = XCreatePixmapCursor(m_display, cursorPixmap, cursorPixmap, &color, &color, 0, 0); + ScopedXcbPtr<xcb_generic_error_t> createCursorError(xcb_request_check( + m_connection, + xcb_create_cursor( + m_connection, + m_hiddenCursor, + cursorPixmap, + cursorPixmap, + 0, 0, 0, // Foreground RGB color + 0, 0, 0, // Background RGB color + 0, // X + 0 // Y + ) + )); + + if (createCursorError) + err() << "Failed to create hidden cursor" << std::endl; // We don't need the pixmap any longer, free it - XFreePixmap(m_display, cursorPixmap); + ScopedXcbPtr<xcb_generic_error_t> freePixmapError(xcb_request_check( + m_connection, + xcb_free_pixmap( + m_connection, + cursorPixmap + ) + )); + + if (freePixmapError) + err() << "Failed to free pixmap for hidden cursor" << std::endl; } @@ -664,26 +1835,7 @@ void WindowImplX11::createHiddenCursor() void WindowImplX11::cleanup() { // Restore the previous video mode (in case we were running in fullscreen) - if (fullscreenWindow == this) - { - // Get current screen info - XRRScreenConfiguration* config = XRRGetScreenInfo(m_display, RootWindow(m_display, m_screen)); - if (config) - { - // Get the current rotation - Rotation currentRotation; - XRRConfigCurrentConfiguration(config, ¤tRotation); - - // Reset the video mode - XRRSetScreenConfig(m_display, config, RootWindow(m_display, m_screen), m_oldVideoMode, currentRotation, CurrentTime); - - // Free the configuration instance - XRRFreeScreenConfigInfo(config); - } - - // Reset the fullscreen window - fullscreenWindow = NULL; - } + resetVideoMode(); // Unhide the mouse cursor (in case it was hidden) setMouseCursorVisible(true); @@ -691,57 +1843,28 @@ void WindowImplX11::cleanup() //////////////////////////////////////////////////////////// -bool WindowImplX11::processEvent(XEvent windowEvent) +bool WindowImplX11::processEvent(xcb_generic_event_t* windowEvent) { - // This function implements a workaround to properly discard - // repeated key events when necessary. The problem is that the - // system's key events policy doesn't match SFML's one: X server will generate - // both repeated KeyPress and KeyRelease events when maintaining a key down, while - // SFML only wants repeated KeyPress events. Thus, we have to: - // - Discard duplicated KeyRelease events when KeyRepeatEnabled is true - // - Discard both duplicated KeyPress and KeyRelease events when KeyRepeatEnabled is false - - // Detect repeated key events - // (code shamelessly taken from SDL) - if (windowEvent.type == KeyRelease) - { - // Check if there's a matching KeyPress event in the queue - XEvent nextEvent; - if (XPending(m_display)) - { - // Grab it but don't remove it from the queue, it still needs to be processed :) - XPeekEvent(m_display, &nextEvent); - if (nextEvent.type == KeyPress) - { - // Check if it is a duplicated event (same timestamp as the KeyRelease event) - if ((nextEvent.xkey.keycode == windowEvent.xkey.keycode) && - (nextEvent.xkey.time - windowEvent.xkey.time < 2)) - { - // If we don't want repeated events, remove the next KeyPress from the queue - if (!m_keyRepeat) - XNextEvent(m_display, &nextEvent); - - // This KeyRelease is a repeated event and we don't want it - return false; - } - } - } - } - // Convert the X11 event to a sf::Event - switch (windowEvent.type) + switch (windowEvent->response_type & ~0x80) { // Destroy event - case DestroyNotify: + case XCB_DESTROY_NOTIFY: { + if (passEvent(windowEvent, reinterpret_cast<xcb_destroy_notify_event_t*>(windowEvent)->window)) + return false; + // The window is about to be destroyed: we must cleanup resources cleanup(); break; } // Gain focus event - case FocusIn: + case XCB_FOCUS_IN: { + if (passEvent(windowEvent, reinterpret_cast<xcb_focus_in_event_t*>(windowEvent)->event)) + return false; + // Update the input context if (m_inputContext) XSetICFocus(m_inputContext); @@ -751,20 +1874,42 @@ bool WindowImplX11::processEvent(XEvent windowEvent) pushEvent(event); // If the window has been previously marked urgent (notification) as a result of a focus request, undo that - XWMHints* hints = XGetWMHints(m_display, m_window); - if (hints != NULL) + ScopedXcbPtr<xcb_generic_error_t> error(NULL); + + ScopedXcbPtr<xcb_get_property_reply_t> hintsReply(xcb_get_property_reply( + m_connection, + xcb_get_property(m_connection, 0, m_window, XCB_ATOM_WM_HINTS, XCB_ATOM_WM_HINTS, 0, 9), + &error + )); + + if (error || !hintsReply) + { + err() << "Failed to get WM hints in XCB_FOCUS_IN" << std::endl; + break; + } + + WMHints* hints = reinterpret_cast<WMHints*>(xcb_get_property_value(hintsReply.get())); + + if (!hints) { - // Remove urgency (notification) flag from hints - hints->flags &= ~XUrgencyHint; - XSetWMHints(m_display, m_window, hints); - XFree(hints); + err() << "Failed to get WM hints in XCB_FOCUS_IN" << std::endl; + break; } + + // Remove urgency (notification) flag from hints + hints->flags &= ~(1 << 8); + + setWMHints(*hints); + break; } // Lost focus event - case FocusOut: + case XCB_FOCUS_OUT: { + if (passEvent(windowEvent, reinterpret_cast<xcb_focus_out_event_t*>(windowEvent)->event)) + return false; + // Update the input context if (m_inputContext) XUnsetICFocus(m_inputContext); @@ -776,64 +1921,110 @@ bool WindowImplX11::processEvent(XEvent windowEvent) } // Resize event - case ConfigureNotify: + case XCB_CONFIGURE_NOTIFY: { - // ConfigureNotify can be triggered for other reasons, check if the size has actually changed - if ((windowEvent.xconfigure.width != m_previousSize.x) || (windowEvent.xconfigure.height != m_previousSize.y)) - { - Event event; - event.type = Event::Resized; - event.size.width = windowEvent.xconfigure.width; - event.size.height = windowEvent.xconfigure.height; - pushEvent(event); + if (passEvent(windowEvent, reinterpret_cast<xcb_configure_notify_event_t*>(windowEvent)->window)) + return false; - m_previousSize.x = windowEvent.xconfigure.width; - m_previousSize.y = windowEvent.xconfigure.height; - } + xcb_configure_notify_event_t* e = reinterpret_cast<xcb_configure_notify_event_t*>(windowEvent); + Event event; + event.type = Event::Resized; + event.size.width = e->width; + event.size.height = e->height; + pushEvent(event); break; } // Close event - case ClientMessage: + case XCB_CLIENT_MESSAGE: { - if ((windowEvent.xclient.format == 32) && (windowEvent.xclient.data.l[0]) == static_cast<long>(m_atomClose)) + if (passEvent(windowEvent, reinterpret_cast<xcb_client_message_event_t*>(windowEvent)->window)) + return false; + + xcb_client_message_event_t* e = reinterpret_cast<xcb_client_message_event_t*>(windowEvent); + + static xcb_atom_t wmProtocols = getAtom("WM_PROTOCOLS"); + + // Handle window manager protocol messages we support + if (e->type == wmProtocols) { - Event event; - event.type = Event::Closed; - pushEvent(event); + static xcb_atom_t wmDeleteWindow = getAtom("WM_DELETE_WINDOW"); + static xcb_atom_t netWmPing = ewmhSupported() ? getAtom("_NET_WM_PING", true) : XCB_ATOM_NONE; + + if ((e->format == 32) && (e->data.data32[0]) == static_cast<long>(wmDeleteWindow)) + { + // Handle the WM_DELETE_WINDOW message + Event event; + event.type = Event::Closed; + pushEvent(event); + } + else if (netWmPing && (e->format == 32) && (e->data.data32[0]) == static_cast<long>(netWmPing)) + { + // Handle the _NET_WM_PING message, send pong back to WM to show that we are responsive + e->window = XCBDefaultRootWindow(m_connection); + + ScopedXcbPtr<xcb_generic_error_t> pongError(xcb_request_check( + m_connection, + xcb_send_event_checked( + m_connection, + 0, + XCBDefaultRootWindow(m_connection), + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, + reinterpret_cast<char*>(e) + ) + )); + + if (pongError) + err() << "Could not send pong event back to WM" << std::endl; + } } break; } // Key down event - case KeyPress: + case XCB_KEY_PRESS: { - // Get the keysym of the key that has been pressed - static XComposeStatus keyboard; - char buffer[32]; - KeySym symbol; - XLookupString(&windowEvent.xkey, buffer, sizeof(buffer), &symbol, &keyboard); + if (passEvent(windowEvent, reinterpret_cast<xcb_key_press_event_t*>(windowEvent)->event)) + return false; + + xcb_key_press_event_t* e = reinterpret_cast<xcb_key_press_event_t*>(windowEvent); // Fill the event parameters // TODO: if modifiers are wrong, use XGetModifierMapping to retrieve the actual modifiers mapping Event event; event.type = Event::KeyPressed; - event.key.code = keysymToSF(symbol); - event.key.alt = windowEvent.xkey.state & Mod1Mask; - event.key.control = windowEvent.xkey.state & ControlMask; - event.key.shift = windowEvent.xkey.state & ShiftMask; - event.key.system = windowEvent.xkey.state & Mod4Mask; + event.key.code = keycodeToSF(e->detail); + event.key.alt = e->state & XCB_MOD_MASK_1; + event.key.control = e->state & XCB_MOD_MASK_CONTROL; + event.key.shift = e->state & XCB_MOD_MASK_SHIFT; + event.key.system = e->state & XCB_MOD_MASK_4; pushEvent(event); + XEvent fakeEvent; + fakeEvent.type = KeyPress; + fakeEvent.xany.display = m_display; + fakeEvent.xany.window = e->event; + fakeEvent.xkey.state = e->state; + fakeEvent.xkey.keycode = e->detail; + // Generate a TextEntered event - if (!XFilterEvent(&windowEvent, None)) + if (!XFilterEvent(&fakeEvent, None)) { #ifdef X_HAVE_UTF8_STRING if (m_inputContext) { Status status; Uint8 keyBuffer[16]; - int length = Xutf8LookupString(m_inputContext, &windowEvent.xkey, reinterpret_cast<char*>(keyBuffer), sizeof(keyBuffer), NULL, &status); + + int length = Xutf8LookupString( + m_inputContext, + &fakeEvent.xkey, + reinterpret_cast<char*>(keyBuffer), + sizeof(keyBuffer), + NULL, + &status + ); + if (length > 0) { Uint32 unicode = 0; @@ -852,7 +2043,7 @@ bool WindowImplX11::processEvent(XEvent windowEvent) { static XComposeStatus status; char keyBuffer[16]; - if (XLookupString(&windowEvent.xkey, keyBuffer, sizeof(keyBuffer), NULL, &status)) + if (XLookupString(&fakeEvent.xkey, keyBuffer, sizeof(keyBuffer), NULL, &status)) { Event textEvent; textEvent.type = Event::TextEntered; @@ -866,43 +2057,54 @@ bool WindowImplX11::processEvent(XEvent windowEvent) } // Key up event - case KeyRelease: + case XCB_KEY_RELEASE: { - // Get the keysym of the key that has been pressed - char buffer[32]; - KeySym symbol; - XLookupString(&windowEvent.xkey, buffer, 32, &symbol, NULL); + if (passEvent(windowEvent, reinterpret_cast<xcb_key_release_event_t*>(windowEvent)->event)) + return false; + + xcb_key_release_event_t* e = reinterpret_cast<xcb_key_release_event_t*>(windowEvent); // Fill the event parameters Event event; event.type = Event::KeyReleased; - event.key.code = keysymToSF(symbol); - event.key.alt = windowEvent.xkey.state & Mod1Mask; - event.key.control = windowEvent.xkey.state & ControlMask; - event.key.shift = windowEvent.xkey.state & ShiftMask; - event.key.system = windowEvent.xkey.state & Mod4Mask; + event.key.code = keycodeToSF(e->detail); + event.key.alt = e->state & XCB_MOD_MASK_1; + event.key.control = e->state & XCB_MOD_MASK_CONTROL; + event.key.shift = e->state & XCB_MOD_MASK_SHIFT; + event.key.system = e->state & XCB_MOD_MASK_4; pushEvent(event); break; } // Mouse button pressed - case ButtonPress: + case XCB_BUTTON_PRESS: { - unsigned int button = windowEvent.xbutton.button; - if ((button == Button1) || (button == Button2) || (button == Button3) || (button == 8) || (button == 9)) + if (passEvent(windowEvent, reinterpret_cast<xcb_button_press_event_t*>(windowEvent)->event)) + return false; + + xcb_button_press_event_t* e = reinterpret_cast<xcb_button_press_event_t*>(windowEvent); + + // XXX: Why button 8 and 9? + // Because 4 and 5 are the vertical wheel and 6 and 7 are horizontal wheel ;) + xcb_button_t button = e->detail; + if ((button == XCB_BUTTON_INDEX_1) || + (button == XCB_BUTTON_INDEX_2) || + (button == XCB_BUTTON_INDEX_3) || + (button == 8) || + (button == 9)) { Event event; event.type = Event::MouseButtonPressed; - event.mouseButton.x = windowEvent.xbutton.x; - event.mouseButton.y = windowEvent.xbutton.y; - switch (button) + event.mouseButton.x = e->event_x; + event.mouseButton.y = e->event_y; + switch(button) { - case Button1: event.mouseButton.button = Mouse::Left; break; - case Button2: event.mouseButton.button = Mouse::Middle; break; - case Button3: event.mouseButton.button = Mouse::Right; break; - case 8: event.mouseButton.button = Mouse::XButton1; break; - case 9: event.mouseButton.button = Mouse::XButton2; break; + case XCB_BUTTON_INDEX_1: event.mouseButton.button = Mouse::Left; break; + case XCB_BUTTON_INDEX_2: event.mouseButton.button = Mouse::Middle; break; + case XCB_BUTTON_INDEX_3: event.mouseButton.button = Mouse::Right; break; + case 8: event.mouseButton.button = Mouse::XButton1; break; + case 9: event.mouseButton.button = Mouse::XButton2; break; } pushEvent(event); } @@ -910,52 +2112,88 @@ bool WindowImplX11::processEvent(XEvent windowEvent) } // Mouse button released - case ButtonRelease: + case XCB_BUTTON_RELEASE: { - unsigned int button = windowEvent.xbutton.button; - if ((button == Button1) || (button == Button2) || (button == Button3) || (button == 8) || (button == 9)) + if (passEvent(windowEvent, reinterpret_cast<xcb_button_release_event_t*>(windowEvent)->event)) + return false; + + xcb_button_release_event_t* e = reinterpret_cast<xcb_button_press_event_t*>(windowEvent); + + xcb_button_t button = e->detail; + if ((button == XCB_BUTTON_INDEX_1) || + (button == XCB_BUTTON_INDEX_2) || + (button == XCB_BUTTON_INDEX_3) || + (button == 8) || + (button == 9)) { Event event; event.type = Event::MouseButtonReleased; - event.mouseButton.x = windowEvent.xbutton.x; - event.mouseButton.y = windowEvent.xbutton.y; - switch (button) + event.mouseButton.x = e->event_x; + event.mouseButton.y = e->event_y; + switch(button) { - case Button1: event.mouseButton.button = Mouse::Left; break; - case Button2: event.mouseButton.button = Mouse::Middle; break; - case Button3: event.mouseButton.button = Mouse::Right; break; - case 8: event.mouseButton.button = Mouse::XButton1; break; - case 9: event.mouseButton.button = Mouse::XButton2; break; + case XCB_BUTTON_INDEX_1: event.mouseButton.button = Mouse::Left; break; + case XCB_BUTTON_INDEX_2: event.mouseButton.button = Mouse::Middle; break; + case XCB_BUTTON_INDEX_3: event.mouseButton.button = Mouse::Right; break; + case 8: event.mouseButton.button = Mouse::XButton1; break; + case 9: event.mouseButton.button = Mouse::XButton2; break; } pushEvent(event); } - else if ((button == Button4) || (button == Button5)) + else if ((button == XCB_BUTTON_INDEX_4) || (button == XCB_BUTTON_INDEX_5)) { Event event; + event.type = Event::MouseWheelMoved; - event.mouseWheel.delta = windowEvent.xbutton.button == Button4 ? 1 : -1; - event.mouseWheel.x = windowEvent.xbutton.x; - event.mouseWheel.y = windowEvent.xbutton.y; + event.mouseWheel.delta = (button == XCB_BUTTON_INDEX_4) ? 1 : -1; + event.mouseWheel.x = e->event_x; + event.mouseWheel.y = e->event_y; + pushEvent(event); + + event.type = Event::MouseWheelScrolled; + event.mouseWheelScroll.wheel = Mouse::VerticalWheel; + event.mouseWheelScroll.delta = (button == XCB_BUTTON_INDEX_4) ? 1 : -1; + event.mouseWheelScroll.x = e->event_x; + event.mouseWheelScroll.y = e->event_y; + pushEvent(event); + } + else if ((button == 6) || (button == 7)) + { + Event event; + event.type = Event::MouseWheelScrolled; + event.mouseWheelScroll.wheel = Mouse::HorizontalWheel; + event.mouseWheelScroll.delta = (button == 6) ? 1 : -1; + event.mouseWheelScroll.x = e->event_x; + event.mouseWheelScroll.y = e->event_y; pushEvent(event); } break; } // Mouse moved - case MotionNotify: + case XCB_MOTION_NOTIFY: { + if (passEvent(windowEvent, reinterpret_cast<xcb_motion_notify_event_t*>(windowEvent)->event)) + return false; + + xcb_motion_notify_event_t* e = reinterpret_cast<xcb_motion_notify_event_t*>(windowEvent); Event event; event.type = Event::MouseMoved; - event.mouseMove.x = windowEvent.xmotion.x; - event.mouseMove.y = windowEvent.xmotion.y; + event.mouseMove.x = e->event_x; + event.mouseMove.y = e->event_y; pushEvent(event); break; } // Mouse entered - case EnterNotify: + case XCB_ENTER_NOTIFY: { - if (windowEvent.xcrossing.mode == NotifyNormal) + if (passEvent(windowEvent, reinterpret_cast<xcb_enter_notify_event_t*>(windowEvent)->event)) + return false; + + xcb_enter_notify_event_t* enterNotifyEvent = reinterpret_cast<xcb_enter_notify_event_t*>(windowEvent); + + if (enterNotifyEvent->mode == NotifyNormal) { Event event; event.type = Event::MouseEntered; @@ -965,9 +2203,14 @@ bool WindowImplX11::processEvent(XEvent windowEvent) } // Mouse left - case LeaveNotify: + case XCB_LEAVE_NOTIFY: { - if (windowEvent.xcrossing.mode == NotifyNormal) + if (passEvent(windowEvent, reinterpret_cast<xcb_leave_notify_event_t*>(windowEvent)->event)) + return false; + + xcb_leave_notify_event_t* leaveNotifyEvent = reinterpret_cast<xcb_leave_notify_event_t*>(windowEvent); + + if (leaveNotifyEvent->mode == NotifyNormal) { Event event; event.type = Event::MouseLeft; @@ -977,9 +2220,91 @@ bool WindowImplX11::processEvent(XEvent windowEvent) } // Parent window changed - case ReparentNotify: + case XCB_REPARENT_NOTIFY: + { + if (passEvent(windowEvent, reinterpret_cast<xcb_reparent_notify_event_t*>(windowEvent)->window)) + return false; + + // Catch reparent events to properly apply fullscreen on + // some "strange" window managers (like Awesome) which + // seem to make use of temporary parents during mapping + if (m_fullscreen) + switchToFullscreen(); + + xcb_flush(m_connection); // Discard remaining events + break; + } + + // The stuff that might pop up but we don't care about + // Whitelist more when necessary + // Window visibility changed (hide/unhide) + case XCB_MAP_NOTIFY: + case XCB_UNMAP_NOTIFY: { - XSync(m_display, True); // Discard remaining events + break; + } + + // Handle the rest + default: + { + uint8_t responseType = windowEvent->response_type & ~0x80; + + // Handle any extension events first + + // DRI2 + static xcb_query_extension_reply_t driExtension = getDriExtension(); + if (driExtension.present) + { + // Because we are using the XCB event queue instead of the Xlib event + // queue, Mesa breaks a bit (VSync among other things) because DRI2 still + // expects certain Xlib events to come its way. We work around this by + // emulating the old Xlib event queue for these specific wire events. + // Sources (retrieved 27 Mar 2015): + // http://wrl.illest.net/post/45342765813/code-tip-glx-and-xcbownseventqueue + // https://bugs.freedesktop.org/show_bug.cgi?id=42131 + // https://bugs.freedesktop.org/show_bug.cgi?id=35945 + // Mesa src/glx/dri2.c + // QtBase src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxintegration.cpp + // QtBase Commit bb22b4965070409df4658f16fdf549f0362e8a9c + // Qt Change-Id I3b4ef3f6e3efbae25f49f161e229e9b15e951778 + // QtBase Commit 13c5c81cfa934c9b610720fe79e07465b00ebc8d + // Qt Change-Id Ia93eb8be1cbbc3d8ae7913a934c195af6b5ec538 + // QtBase Commit decb88693c8a7f0c073889b91151f01a850e3adf + // Qt Change-Id Ic466ff26487937b03f072a57e0ee4df335492a5f + if ((responseType == driExtension.first_event + XCB_DRI2_BUFFER_SWAP_COMPLETE) || + (responseType == driExtension.first_event + XCB_DRI2_INVALIDATE_BUFFERS)) + { + // DRI2 BufferSwapComplete and InvalidateBuffers + + // We lock/unlock the display to protect against concurrent access + XLockDisplay(m_display); + + typedef Bool (*wireEventHandler)(Display*, XEvent*, xEvent*); + + // Probe for any handlers that are registered for this event type + wireEventHandler handler = XESetWireToEvent(m_display, responseType, 0); + + if (handler) + { + // Restore the previous handler if one was registered + XESetWireToEvent(m_display, responseType, handler); + + XEvent event; + windowEvent->sequence = LastKnownRequestProcessed(m_display); + + // Pretend to be the Xlib event queue + handler(m_display, &event, reinterpret_cast<xEvent*>(windowEvent)); + } + + XUnlockDisplay(m_display); + + return true; + } + } + + // Print any surprises to stderr (would be nice if people report when this happens) + dumpUnhandledEvent(responseType); + break; } } @@ -989,119 +2314,29 @@ bool WindowImplX11::processEvent(XEvent windowEvent) //////////////////////////////////////////////////////////// -Keyboard::Key WindowImplX11::keysymToSF(KeySym symbol) +bool WindowImplX11::passEvent(xcb_generic_event_t* windowEvent, xcb_window_t window) { - // First convert to uppercase (to avoid dealing with two different keysyms for the same key) - KeySym lower, key; - XConvertCase(symbol, &lower, &key); + // Check if this is our event + if (window == m_window) + return false; + + Lock lock(allWindowsMutex); - switch (key) + // If we are the only window, there is nobody else to pass to + if (allWindows.size() == 1) + return false; + + for (std::vector<WindowImplX11*>::iterator i = allWindows.begin(); i != allWindows.end(); ++i) { - case XK_Shift_L: return Keyboard::LShift; - case XK_Shift_R: return Keyboard::RShift; - case XK_Control_L: return Keyboard::LControl; - case XK_Control_R: return Keyboard::RControl; - case XK_Alt_L: return Keyboard::LAlt; - case XK_Alt_R: return Keyboard::RAlt; - case XK_Super_L: return Keyboard::LSystem; - case XK_Super_R: return Keyboard::RSystem; - case XK_Menu: return Keyboard::Menu; - case XK_Escape: return Keyboard::Escape; - case XK_semicolon: return Keyboard::SemiColon; - case XK_slash: return Keyboard::Slash; - case XK_equal: return Keyboard::Equal; - case XK_minus: return Keyboard::Dash; - case XK_bracketleft: return Keyboard::LBracket; - case XK_bracketright: return Keyboard::RBracket; - case XK_comma: return Keyboard::Comma; - case XK_period: return Keyboard::Period; - case XK_dead_acute: return Keyboard::Quote; - case XK_backslash: return Keyboard::BackSlash; - case XK_dead_grave: return Keyboard::Tilde; - case XK_space: return Keyboard::Space; - case XK_Return: return Keyboard::Return; - case XK_KP_Enter: return Keyboard::Return; - case XK_BackSpace: return Keyboard::BackSpace; - case XK_Tab: return Keyboard::Tab; - case XK_Prior: return Keyboard::PageUp; - case XK_Next: return Keyboard::PageDown; - case XK_End: return Keyboard::End; - case XK_Home: return Keyboard::Home; - case XK_Insert: return Keyboard::Insert; - case XK_Delete: return Keyboard::Delete; - case XK_KP_Add: return Keyboard::Add; - case XK_KP_Subtract: return Keyboard::Subtract; - case XK_KP_Multiply: return Keyboard::Multiply; - case XK_KP_Divide: return Keyboard::Divide; - case XK_Pause: return Keyboard::Pause; - case XK_F1: return Keyboard::F1; - case XK_F2: return Keyboard::F2; - case XK_F3: return Keyboard::F3; - case XK_F4: return Keyboard::F4; - case XK_F5: return Keyboard::F5; - case XK_F6: return Keyboard::F6; - case XK_F7: return Keyboard::F7; - case XK_F8: return Keyboard::F8; - case XK_F9: return Keyboard::F9; - case XK_F10: return Keyboard::F10; - case XK_F11: return Keyboard::F11; - case XK_F12: return Keyboard::F12; - case XK_F13: return Keyboard::F13; - case XK_F14: return Keyboard::F14; - case XK_F15: return Keyboard::F15; - case XK_Left: return Keyboard::Left; - case XK_Right: return Keyboard::Right; - case XK_Up: return Keyboard::Up; - case XK_Down: return Keyboard::Down; - case XK_KP_0: return Keyboard::Numpad0; - case XK_KP_1: return Keyboard::Numpad1; - case XK_KP_2: return Keyboard::Numpad2; - case XK_KP_3: return Keyboard::Numpad3; - case XK_KP_4: return Keyboard::Numpad4; - case XK_KP_5: return Keyboard::Numpad5; - case XK_KP_6: return Keyboard::Numpad6; - case XK_KP_7: return Keyboard::Numpad7; - case XK_KP_8: return Keyboard::Numpad8; - case XK_KP_9: return Keyboard::Numpad9; - case XK_A: return Keyboard::A; - case XK_Z: return Keyboard::Z; - case XK_E: return Keyboard::E; - case XK_R: return Keyboard::R; - case XK_T: return Keyboard::T; - case XK_Y: return Keyboard::Y; - case XK_U: return Keyboard::U; - case XK_I: return Keyboard::I; - case XK_O: return Keyboard::O; - case XK_P: return Keyboard::P; - case XK_Q: return Keyboard::Q; - case XK_S: return Keyboard::S; - case XK_D: return Keyboard::D; - case XK_F: return Keyboard::F; - case XK_G: return Keyboard::G; - case XK_H: return Keyboard::H; - case XK_J: return Keyboard::J; - case XK_K: return Keyboard::K; - case XK_L: return Keyboard::L; - case XK_M: return Keyboard::M; - case XK_W: return Keyboard::W; - case XK_X: return Keyboard::X; - case XK_C: return Keyboard::C; - case XK_V: return Keyboard::V; - case XK_B: return Keyboard::B; - case XK_N: return Keyboard::N; - case XK_0: return Keyboard::Num0; - case XK_1: return Keyboard::Num1; - case XK_2: return Keyboard::Num2; - case XK_3: return Keyboard::Num3; - case XK_4: return Keyboard::Num4; - case XK_5: return Keyboard::Num5; - case XK_6: return Keyboard::Num6; - case XK_7: return Keyboard::Num7; - case XK_8: return Keyboard::Num8; - case XK_9: return Keyboard::Num9; + if ((*i)->m_window == window) + { + (*i)->m_xcbEvents.push_back(windowEvent); + return true; + } } - return Keyboard::Unknown; + // It seems nobody wants the event, we'll just handle it ourselves + return false; } } // namespace priv diff --git a/src/SFML/Window/Unix/WindowImplX11.hpp b/src/SFML/Window/Unix/WindowImplX11.hpp index d824492..2e31fbf 100644 --- a/src/SFML/Window/Unix/WindowImplX11.hpp +++ b/src/SFML/Window/Unix/WindowImplX11.hpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library -// Copyright (C) 2007-2014 Laurent Gomila (laurent.gom@gmail.com) +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the use of this software. @@ -31,8 +31,9 @@ #include <SFML/Window/Event.hpp> #include <SFML/Window/WindowImpl.hpp> #include <SFML/System/String.hpp> -#include <X11/Xlib.h> -#include <set> +#include <X11/Xlib-xcb.h> +#include <xcb/randr.h> +#include <deque> namespace sf @@ -179,13 +180,100 @@ protected: private: + struct WMHints + { + int32_t flags; + uint32_t input; + int32_t initial_state; + xcb_pixmap_t icon_pixmap; + xcb_window_t icon_window; + int32_t icon_x; + int32_t icon_y; + xcb_pixmap_t icon_mask; + xcb_window_t window_group; + }; + + struct WMSizeHints + { + uint32_t flags; + int32_t x, y; + int32_t width, height; + int32_t min_width, min_height; + int32_t max_width, max_height; + int32_t width_inc, height_inc; + int32_t min_aspect_num, min_aspect_den; + int32_t max_aspect_num, max_aspect_den; + int32_t base_width, base_height; + uint32_t win_gravity; + }; + //////////////////////////////////////////////////////////// - /// \brief Switch to fullscreen mode + /// \brief Request the WM to make the current window active + /// + //////////////////////////////////////////////////////////// + void grabFocus(); + + //////////////////////////////////////////////////////////// + /// \brief Set fullscreen video mode /// /// \param Mode video mode to switch to /// //////////////////////////////////////////////////////////// - void switchToFullscreen(const VideoMode& mode); + void setVideoMode(const VideoMode& mode); + + //////////////////////////////////////////////////////////// + /// \brief Reset to desktop video mode + /// + //////////////////////////////////////////////////////////// + void resetVideoMode(); + + //////////////////////////////////////////////////////////// + /// \brief Switch to fullscreen mode + /// + //////////////////////////////////////////////////////////// + void switchToFullscreen(); + + //////////////////////////////////////////////////////////// + /// \brief Set the WM protocols we support + /// + //////////////////////////////////////////////////////////// + void setProtocols(); + + //////////////////////////////////////////////////////////// + /// \brief Set Motif WM hints + /// + //////////////////////////////////////////////////////////// + void setMotifHints(unsigned long style); + + //////////////////////////////////////////////////////////// + /// \brief Set WM hints + /// + /// \param hints Hints + /// + //////////////////////////////////////////////////////////// + void setWMHints(const WMHints& hints); + + //////////////////////////////////////////////////////////// + /// \brief Set WM size hints + /// + /// \param hints Size hints + /// + //////////////////////////////////////////////////////////// + void setWMSizeHints(const WMSizeHints& hints); + + //////////////////////////////////////////////////////////// + /// \brief Change a XCB window property + /// + /// \param property Property to change + /// \param type Type of the property + /// \param format Format of the property + /// \param length Length of the new value + /// \param data The new value of the property + /// + /// \return True if successful, false if unsuccessful + /// + //////////////////////////////////////////////////////////// + bool changeWindowProperty(xcb_atom_t property, xcb_atom_t type, uint8_t format, uint32_t length, const void* data); //////////////////////////////////////////////////////////// /// \brief Do some common initializations after the window has been created @@ -213,33 +301,36 @@ private: /// \return True if the event was processed, false if it was discarded /// //////////////////////////////////////////////////////////// - bool processEvent(XEvent windowEvent); + bool processEvent(xcb_generic_event_t* windowEvent); //////////////////////////////////////////////////////////// - /// \brief Convert a X11 keysym to SFML key code + /// \brief Pass an incoming event to another window /// - /// \param symbol Key symbol to convert + /// \param windowEvent Event which is being processed + /// \param window Window to pass to /// - /// \return Corresponding SFML key code + /// \return True if the event was passed to another window, false if it is destined for the current window /// //////////////////////////////////////////////////////////// - static Keyboard::Key keysymToSF(KeySym symbol); + bool passEvent(xcb_generic_event_t* windowEvent, xcb_window_t window); //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// - ::Window m_window; ///< X11 structure defining our window - ::Display* m_display; ///< Pointer to the display - int m_screen; ///< Screen identifier - XIM m_inputMethod; ///< Input method linked to the X display - XIC m_inputContext; ///< Input context used to get Unicode input in our window - bool m_isExternal; ///< Tell whether the window has been created externally or by SFML - Atom m_atomClose; ///< Atom used to identify the close event - int m_oldVideoMode; ///< Video mode in use before we switch to fullscreen - Cursor m_hiddenCursor; ///< As X11 doesn't provide cursor hiding, we must create a transparent one - bool m_keyRepeat; ///< Is the KeyRepeat feature enabled? - Vector2i m_previousSize; ///< Previous size of the window, to find if a ConfigureNotify event is a resize event (could be a move event only) - bool m_useSizeHints; ///< Is the size of the window fixed with size hints? + xcb_window_t m_window; ///< xcb identifier defining our window + ::Display* m_display; ///< Pointer to the display + xcb_connection_t* m_connection; ///< Pointer to the xcb connection + xcb_screen_t* m_screen; ///< Screen identifier + XIM m_inputMethod; ///< Input method linked to the X display + XIC m_inputContext; ///< Input context used to get unicode input in our window + std::deque<xcb_generic_event_t*> m_xcbEvents; ///< Events that were received in another window's loop + bool m_isExternal; ///< Tell whether the window has been created externally or by SFML + xcb_randr_get_screen_info_reply_t m_oldVideoMode; ///< Video mode in use before we switch to fullscreen + Cursor m_hiddenCursor; ///< As X11 doesn't provide cursor hidding, we must create a transparent one + bool m_keyRepeat; ///< Is the KeyRepeat feature enabled? + Vector2i m_previousSize; ///< Previous size of the window, to find if a ConfigureNotify event is a resize event (could be a move event only) + bool m_useSizeHints; ///< Is the size of the window fixed with size hints? + bool m_fullscreen; ///< Is window in fullscreen? }; } // namespace priv |