diff --git a/include/Nazara/Utility/WindowHandle.hpp b/include/Nazara/Utility/WindowHandle.hpp index bebbc1107..6069e3247 100644 --- a/include/Nazara/Utility/WindowHandle.hpp +++ b/include/Nazara/Utility/WindowHandle.hpp @@ -13,8 +13,9 @@ // http://msdn.microsoft.com/en-us/library/aa383751(v=vs.85).aspx typedef void* NzWindowHandle; #elif defined(NAZARA_PLATFORM_LINUX) +#include // http://en.wikipedia.org/wiki/Xlib#Data_types -typedef unsigned long NzWindowHandle; +using NzWindowHandle = xcb_window_t; #else #error Lack of implementation: WindowHandle #endif diff --git a/src/Nazara/Utility/Cursor.cpp b/src/Nazara/Utility/Cursor.cpp index 7a14fd921..cff05c33a 100644 --- a/src/Nazara/Utility/Cursor.cpp +++ b/src/Nazara/Utility/Cursor.cpp @@ -6,8 +6,8 @@ #if defined(NAZARA_PLATFORM_WINDOWS) #include -#elif defined(NAZARA_PLATFORM_LINUX) - #include +#elif defined(NAZARA_PLATFORM_X11) + #include #else #error Lack of implementation: Cursor #endif diff --git a/src/Nazara/Utility/Icon.cpp b/src/Nazara/Utility/Icon.cpp index ffd2b4f67..b67b42076 100644 --- a/src/Nazara/Utility/Icon.cpp +++ b/src/Nazara/Utility/Icon.cpp @@ -6,8 +6,8 @@ #if defined(NAZARA_PLATFORM_WINDOWS) #include -#elif defined(NAZARA_PLATFORM_LINUX) - #include +#elif defined(NAZARA_PLATFORM_X11) + #include #else #error Lack of implementation: Icon #endif diff --git a/src/Nazara/Utility/Keyboard.cpp b/src/Nazara/Utility/Keyboard.cpp index 28faa4db7..5d29a0a0e 100644 --- a/src/Nazara/Utility/Keyboard.cpp +++ b/src/Nazara/Utility/Keyboard.cpp @@ -6,8 +6,8 @@ #if defined(NAZARA_PLATFORM_WINDOWS) #include -#elif defined(NAZARA_PLATFORM_LINUX) - #include +#elif defined(NAZARA_PLATFORM_X11) + #include #else #error Lack of implementation: Keyboard #endif diff --git a/src/Nazara/Utility/Mouse.cpp b/src/Nazara/Utility/Mouse.cpp index cdd8be2f1..cb720b022 100644 --- a/src/Nazara/Utility/Mouse.cpp +++ b/src/Nazara/Utility/Mouse.cpp @@ -7,8 +7,8 @@ #if defined(NAZARA_PLATFORM_WINDOWS) #include -#elif defined(NAZARA_PLATFORM_LINUX) - #include +#elif defined(NAZARA_PLATFORM_X11) + #include #else #error Lack of implementation: Mouse #endif diff --git a/src/Nazara/Utility/VideoMode.cpp b/src/Nazara/Utility/VideoMode.cpp index 69abdf4a1..4ca8ea532 100644 --- a/src/Nazara/Utility/VideoMode.cpp +++ b/src/Nazara/Utility/VideoMode.cpp @@ -3,9 +3,17 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include -#include #include #include + +#if defined(NAZARA_PLATFORM_WINDOWS) + #include +#elif defined(NAZARA_PLATFORM_X11) + #include +#else + #error Lack of implementation: Window +#endif + #include NzVideoMode::NzVideoMode() : diff --git a/src/Nazara/Utility/Win32/VideoModeImpl.cpp b/src/Nazara/Utility/Win32/VideoModeImpl.cpp index b6d03bb96..c9b1d8489 100644 --- a/src/Nazara/Utility/Win32/VideoModeImpl.cpp +++ b/src/Nazara/Utility/Win32/VideoModeImpl.cpp @@ -2,7 +2,7 @@ // This file is part of the "Nazara Engine - Utility module" // For conditions of distribution and use, see copyright notice in Config.hpp -#include +#include #include #include #include diff --git a/src/Nazara/Utility/Win32/VideoModeImpl.hpp b/src/Nazara/Utility/Win32/VideoModeImpl.hpp new file mode 100644 index 000000000..3a53f5e94 --- /dev/null +++ b/src/Nazara/Utility/Win32/VideoModeImpl.hpp @@ -0,0 +1,19 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_VIDEOMODEIMPL_HPP +#define NAZARA_VIDEOMODEIMPL_HPP + +#include + +class NzVideoModeImpl +{ + public: + static NzVideoMode GetDesktopMode(); + static void GetFullscreenModes(std::vector& modes); +}; + +#endif // NNAZARA_VIDEOMODEIMPL_HPP diff --git a/src/Nazara/Utility/Window.cpp b/src/Nazara/Utility/Window.cpp index 673396889..e390aac5e 100644 --- a/src/Nazara/Utility/Window.cpp +++ b/src/Nazara/Utility/Window.cpp @@ -13,8 +13,8 @@ #if defined(NAZARA_PLATFORM_WINDOWS) #include -#elif defined(NAZARA_PLATFORM_LINUX) - #include +#elif defined(NAZARA_PLATFORM_X11) + #include #else #error Lack of implementation: Window #endif diff --git a/src/Nazara/Utility/X11/CursorImpl.cpp b/src/Nazara/Utility/X11/CursorImpl.cpp new file mode 100644 index 000000000..1ccde5e70 --- /dev/null +++ b/src/Nazara/Utility/X11/CursorImpl.cpp @@ -0,0 +1,161 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool NzCursorImpl::Create(const NzImage& cursor, int hotSpotX, int hotSpotY) +{ + NzImage cursorImage(cursor); // Vive le COW + if (!cursorImage.Convert(nzPixelFormat_BGRA8)) + { + NazaraError("Failed to convert cursor to BGRA8"); + return false; + } + + auto width = cursorImage.GetWidth(); + auto height = cursorImage.GetHeight(); + + NzScopedXCBConnection connection; + + xcb_screen_t* screen = X11::XCBDefaultScreen(connection); + + NzScopedXCB error(nullptr); + NzScopedXCB formatsReply = xcb_render_query_pict_formats_reply( + connection, + xcb_render_query_pict_formats(connection), + &error); + + if (!formatsReply || error) + { + NazaraError("Failed to get pict formats"); + return false; + } + + xcb_render_pictforminfo_t* fmt = xcb_render_util_find_standard_format( + formatsReply.get(), + XCB_PICT_STANDARD_ARGB_32); + + if (!fmt) + { + NazaraError("Failed to find format PICT_STANDARD_ARGB_32"); + return false; + } + + xcb_image_t* xi = xcb_image_create( + width, height, + XCB_IMAGE_FORMAT_Z_PIXMAP, + 32, 32, 32, 32, + XCB_IMAGE_ORDER_LSB_FIRST, + XCB_IMAGE_ORDER_MSB_FIRST, + 0, 0, 0); + + if (!xi) + { + NazaraError("Failed to create image for cursor"); + return false; + } + + std::unique_ptr data(new uint8_t[xi->stride * height]); + + if (!data) + { + xcb_image_destroy(xi); + NazaraError("Failed to allocate memory for cursor image"); + return false; + } + + xi->data = data.get(); + + std::copy(cursorImage.GetConstPixels(), cursorImage.GetConstPixels() + cursorImage.GetBytesPerPixel() * width * height, xi->data); + + xcb_render_picture_t pic = XCB_NONE; + + NzCallOnExit onExit([&](){ + xcb_image_destroy(xi); + if (pic != XCB_NONE) + xcb_render_free_picture(connection, pic); + }); + + NzXCBPixmap pix(connection); + if (!pix.Create(32, screen->root, width, height)) + { + NazaraError("Failed to create pixmap for cursor"); + return false; + } + + pic = xcb_generate_id(connection); + if (!X11::CheckCookie( + connection, + xcb_render_create_picture( + connection, + pic, + pix, + fmt->id, + 0, + nullptr + ))) + { + NazaraError("Failed to create render picture for cursor"); + return false; + } + + NzXCBGContext gc(connection); + if (!gc.Create(pix, 0, nullptr)) + { + NazaraError("Failed to create gcontext for cursor"); + return false; + } + + if (!X11::CheckCookie( + connection, + xcb_image_put( + connection, + pix, + gc, + xi, + 0, 0, + 0 + ))) + { + NazaraError("Failed to put image for cursor"); + return false; + } + + m_cursor = xcb_generate_id(connection); + if (!X11::CheckCookie( + connection, + xcb_render_create_cursor( + connection, + m_cursor, + pic, + hotSpotX, hotSpotY + ))) + { + NazaraError("Failed to create cursor"); + return false; + } + + return true; +} + +void NzCursorImpl::Destroy() +{ + NzScopedXCBConnection connection; + + xcb_free_cursor(connection, m_cursor); + m_cursor = 0; +} + +xcb_cursor_t NzCursorImpl::GetCursor() +{ + return m_cursor; +} diff --git a/src/Nazara/Utility/X11/CursorImpl.hpp b/src/Nazara/Utility/X11/CursorImpl.hpp new file mode 100644 index 000000000..b20555623 --- /dev/null +++ b/src/Nazara/Utility/X11/CursorImpl.hpp @@ -0,0 +1,26 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_CURSORIMPL_HPP +#define NAZARA_CURSORIMPL_HPP + +#include + +class NzImage; + +class NzCursorImpl +{ + public: + bool Create(const NzImage& image, int hotSpotX, int hotSpotY); + void Destroy(); + + xcb_cursor_t GetCursor(); + + private: + xcb_cursor_t m_cursor; +}; + +#endif // NAZARA_CURSORIMPL_HPP diff --git a/src/Nazara/Utility/X11/Display.cpp b/src/Nazara/Utility/X11/Display.cpp new file mode 100644 index 000000000..99e0abe03 --- /dev/null +++ b/src/Nazara/Utility/X11/Display.cpp @@ -0,0 +1,218 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include + +namespace +{ + // The shared display and its reference counter + xcb_connection_t* sharedConnection = nullptr; + int screen_nbr = 0; + unsigned int referenceCountConnection = 0; + + xcb_key_symbols_t* sharedkeySymbol; + unsigned int referenceCountKeySymbol = 0; + + xcb_ewmh_connection_t* sharedEwmhConnection; + unsigned int referenceCountEwmhConnection = 0; + + using AtomMap = std::map; + AtomMap atoms; +} + +namespace X11 +{ + bool CheckCookie(xcb_connection_t* connection, xcb_void_cookie_t cookie) + { + NzScopedXCB error(xcb_request_check( + connection, + cookie + )); + + if (error) + return false; + else + return true; + } + + void CloseConnection(xcb_connection_t* connection) + { + NazaraAssert(connection == sharedConnection, "The model is meant for one connection to X11 server"); + --referenceCountConnection; + } + + void CloseEWMHConnection(xcb_ewmh_connection_t* ewmh_connection) + { + NazaraAssert(ewmh_connection == sharedEwmhConnection, "The model is meant for one connection to X11 server"); + --referenceCountEwmhConnection; + } + + xcb_atom_t GetAtom(const std::string& name, bool onlyIfExists) + { + AtomMap::const_iterator iter = atoms.find(name); + + if (iter != atoms.end()) + return iter->second; + + NzScopedXCB error(nullptr); + + xcb_connection_t* connection = OpenConnection(); + + NzScopedXCB reply(xcb_intern_atom_reply( + connection, + xcb_intern_atom( + connection, + onlyIfExists, + name.size(), + name.c_str() + ), + &error + )); + + CloseConnection(connection); + + if (error || !reply) + { + NazaraError("Failed to get " + name + " atom."); + return XCB_ATOM_NONE; + } + + atoms[name] = reply->atom; + + return reply->atom; + } + + void Initialize() + { + NazaraAssert(referenceCountConnection == 0, "Initialize should be called before anything"); + NazaraAssert(referenceCountKeySymbol == 0, "Initialize should be called before anything"); + NazaraAssert(referenceCountEwmhConnection == 0, "Initialize should be called before anything"); + + { + sharedConnection = xcb_connect(nullptr, &screen_nbr); + + // Opening display failed: The best we can do at the moment is to output a meaningful error message + // and cause an abnormal program termination + if (!sharedConnection || xcb_connection_has_error(sharedConnection)) + { + NazaraError("Failed to open xcb connection"); + std::abort(); + } + + OpenConnection(); + } + + { + sharedkeySymbol = xcb_key_symbols_alloc(sharedConnection); + + XCBKeySymbolsAlloc(sharedConnection); + } + + { + sharedEwmhConnection = new xcb_ewmh_connection_t; + xcb_intern_atom_cookie_t* ewmh_cookie = xcb_ewmh_init_atoms(sharedConnection, sharedEwmhConnection); + + if(!xcb_ewmh_init_atoms_replies(sharedEwmhConnection, ewmh_cookie, nullptr)) + { + NazaraError("Could not initialize EWMH Connection"); + sharedEwmhConnection = nullptr; + } + + OpenEWMHConnection(sharedConnection); + } + } + + xcb_key_symbols_t* XCBKeySymbolsAlloc(xcb_connection_t* connection) + { + NazaraAssert(connection == sharedConnection, "The model is meant for one connection to X11 server"); + + ++referenceCountKeySymbol; + return sharedkeySymbol; + } + + void XCBKeySymbolsFree(xcb_key_symbols_t* keySymbols) + { + NazaraAssert(keySymbols == sharedkeySymbol, "The model is meant for one connection to X11 server"); + + --referenceCountKeySymbol; + } + + xcb_connection_t* OpenConnection() + { + ++referenceCountConnection; + return sharedConnection; + } + + xcb_ewmh_connection_t* OpenEWMHConnection(xcb_connection_t* connection) + { + NazaraAssert(connection == sharedConnection, "The model is meant for one connection to X11 server"); + + ++referenceCountEwmhConnection; + return sharedEwmhConnection; + } + + void Uninitialize() + { + { + NazaraAssert(referenceCountEwmhConnection == 1, "Uninitialize should be called after anything or a close is missing"); + CloseEWMHConnection(sharedEwmhConnection); + + xcb_ewmh_connection_wipe(sharedEwmhConnection); + delete sharedEwmhConnection; + } + + { + NazaraAssert(referenceCountKeySymbol == 1, "Uninitialize should be called after anything or a free is missing"); + XCBKeySymbolsFree(sharedkeySymbol); + + xcb_key_symbols_free(sharedkeySymbol); + } + + { + NazaraAssert(referenceCountConnection == 1, "Uninitialize should be called after anything or a close is missing"); + CloseConnection(sharedConnection); + + xcb_disconnect(sharedConnection); + } + } + + xcb_window_t XCBDefaultRootWindow(xcb_connection_t* connection) + { + NazaraAssert(connection == sharedConnection, "The model is meant for one connection to X11 server"); + xcb_screen_t* screen = XCBDefaultScreen(connection); + if (screen) + return screen->root; + return XCB_NONE; + } + + xcb_screen_t* XCBDefaultScreen(xcb_connection_t* connection) + { + NazaraAssert(connection == sharedConnection, "The model is meant for one connection to X11 server"); + return XCBScreenOfDisplay(connection, screen_nbr); + } + + int XCBScreen(xcb_connection_t* connection) + { + NazaraAssert(connection == sharedConnection, "The model is meant for one connection to X11 server"); + return screen_nbr; + } + + xcb_screen_t* XCBScreenOfDisplay(xcb_connection_t* connection, int screen_nbr) + { + NazaraAssert(connection == sharedConnection, "The model is meant for one connection to X11 server"); + 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 nullptr; + } +} diff --git a/src/Nazara/Utility/X11/Display.hpp b/src/Nazara/Utility/X11/Display.hpp new file mode 100644 index 000000000..a1b0e56e5 --- /dev/null +++ b/src/Nazara/Utility/X11/Display.hpp @@ -0,0 +1,41 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_X11DISPLAY_HPP +#define NAZARA_X11DISPLAY_HPP + +#include +#include +#include +#include + +typedef struct _XCBKeySymbols xcb_key_symbols_t; + +namespace X11 +{ + bool CheckCookie(xcb_connection_t* connection, xcb_void_cookie_t cookie); + void CloseConnection(xcb_connection_t* connection); + void CloseEWMHConnection(xcb_ewmh_connection_t* ewmh_connection); + + xcb_atom_t GetAtom(const std::string& name, bool onlyIfExists = false); + + void Initialize(); + + xcb_key_symbols_t* XCBKeySymbolsAlloc(xcb_connection_t* connection); + void XCBKeySymbolsFree(xcb_key_symbols_t* keySymbols); + + xcb_connection_t* OpenConnection(); + xcb_ewmh_connection_t* OpenEWMHConnection(xcb_connection_t* connection); + + void Uninitialize(); + + xcb_screen_t* XCBDefaultScreen(xcb_connection_t* connection); + xcb_window_t XCBDefaultRootWindow(xcb_connection_t* connection); + int XCBScreen(xcb_connection_t* connection); + xcb_screen_t* XCBScreenOfDisplay(xcb_connection_t* connection, int screen_nbr); +} + +#endif // NAZARA_X11DISPLAY_HPP diff --git a/src/Nazara/Utility/X11/IconImpl.cpp b/src/Nazara/Utility/X11/IconImpl.cpp new file mode 100644 index 000000000..3eb3bd1ab --- /dev/null +++ b/src/Nazara/Utility/X11/IconImpl.cpp @@ -0,0 +1,134 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include + +NzIconImpl::NzIconImpl() +{ + NzScopedXCBConnection connection; + + m_iconPixmap.Connect(connection); + m_maskPixmap.Connect(connection); +} + +bool NzIconImpl::Create(const NzImage& icon) +{ + NzImage iconImage(icon); // Vive le COW + if (!iconImage.Convert(nzPixelFormat_BGRA8)) + { + NazaraError("Failed to convert icon to BGRA8"); + return false; + } + + auto width = iconImage.GetWidth(); + auto height = iconImage.GetHeight(); + + NzScopedXCBConnection connection; + + xcb_screen_t* screen = X11::XCBDefaultScreen(connection); + + if (!m_iconPixmap.Create( + screen->root_depth, + screen->root, + width, + height)) + { + NazaraError("Failed to create icon pixmap"); + return false; + } + + NzCallOnExit onExit([this](){ + Destroy(); + }); + + NzXCBGContext iconGC(connection); + + if (!iconGC.Create( + m_iconPixmap, + 0, + nullptr)) + { + NazaraError("Failed to create icon gc"); + return false; + } + + if (!X11::CheckCookie( + connection, + xcb_put_image( + connection, + XCB_IMAGE_FORMAT_Z_PIXMAP, + m_iconPixmap, + iconGC, + width, + height, + 0, + 0, + 0, + screen->root_depth, + width * height * 4, + iconImage.GetConstPixels() + ))) + { + NazaraError("Failed to put image for icon"); + return false; + } + + // Create the mask pixmap (must have 1 bit depth) + std::size_t pitch = (width + 7) / 8; + static std::vector maskPixels(pitch * height, 0); + for (std::size_t j = 0; j < height; ++j) + { + for (std::size_t i = 0; i < pitch; ++i) + { + for (std::size_t k = 0; k < 8; ++k) + { + if (i * 8 + k < width) + { + nzUInt8 opacity = (iconImage.GetConstPixels()[(i * 8 + k + j * width) * 4 + 3] > 0) ? 1 : 0; + maskPixels[i + j * pitch] |= (opacity << k); + } + } + } + } + + if (!m_maskPixmap.CreatePixmapFromBitmapData( + X11::XCBDefaultRootWindow(connection), + reinterpret_cast(&maskPixels[0]), + width, + height, + 1, + 0, + 1, + nullptr)) + { + NazaraError("Failed to create mask pixmap for icon"); + return false; + } + + onExit.Reset(); + + return true; +} + +void NzIconImpl::Destroy() +{ + m_iconPixmap.Destroy(); + m_maskPixmap.Destroy(); +} + +xcb_pixmap_t NzIconImpl::GetIcon() +{ + return m_iconPixmap; +} + +xcb_pixmap_t NzIconImpl::GetMask() +{ + return m_maskPixmap; +} diff --git a/src/Nazara/Utility/X11/IconImpl.hpp b/src/Nazara/Utility/X11/IconImpl.hpp new file mode 100644 index 000000000..bf968f367 --- /dev/null +++ b/src/Nazara/Utility/X11/IconImpl.hpp @@ -0,0 +1,30 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_ICONIMPL_HPP +#define NAZARA_ICONIMPL_HPP + +#include + +class NzImage; + +class NzIconImpl +{ + public: + NzIconImpl(); + + bool Create(const NzImage& image); + void Destroy(); + + xcb_pixmap_t GetIcon(); + xcb_pixmap_t GetMask(); + + private: + NzXCBPixmap m_iconPixmap; + NzXCBPixmap m_maskPixmap; +}; + +#endif // NAZARA_ICONIMPL_HPP diff --git a/src/Nazara/Utility/X11/InputImpl.cpp b/src/Nazara/Utility/X11/InputImpl.cpp new file mode 100644 index 000000000..442b1e544 --- /dev/null +++ b/src/Nazara/Utility/X11/InputImpl.cpp @@ -0,0 +1,371 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + + KeySym GetKeySym(NzKeyboard::Key key) + { + // X11 keysym correspondant + KeySym keysym = 0; + switch (key) + { + // Lettres + case NzKeyboard::A: keysym = XK_A; break; + case NzKeyboard::B: keysym = XK_B; break; + case NzKeyboard::C: keysym = XK_C; break; + case NzKeyboard::D: keysym = XK_D; break; + case NzKeyboard::E: keysym = XK_E; break; + case NzKeyboard::F: keysym = XK_F; break; + case NzKeyboard::G: keysym = XK_G; break; + case NzKeyboard::H: keysym = XK_H; break; + case NzKeyboard::I: keysym = XK_I; break; + case NzKeyboard::J: keysym = XK_J; break; + case NzKeyboard::K: keysym = XK_K; break; + case NzKeyboard::L: keysym = XK_L; break; + case NzKeyboard::M: keysym = XK_M; break; + case NzKeyboard::N: keysym = XK_N; break; + case NzKeyboard::O: keysym = XK_O; break; + case NzKeyboard::P: keysym = XK_P; break; + case NzKeyboard::Q: keysym = XK_Q; break; + case NzKeyboard::R: keysym = XK_R; break; + case NzKeyboard::S: keysym = XK_S; break; + case NzKeyboard::T: keysym = XK_T; break; + case NzKeyboard::U: keysym = XK_U; break; + case NzKeyboard::V: keysym = XK_V; break; + case NzKeyboard::W: keysym = XK_W; break; + case NzKeyboard::X: keysym = XK_X; break; + case NzKeyboard::Y: keysym = XK_Y; break; + case NzKeyboard::Z: keysym = XK_Z; break; + + // Touches de fonction + case NzKeyboard::F1: keysym = XK_F1; break; + case NzKeyboard::F2: keysym = XK_F2; break; + case NzKeyboard::F3: keysym = XK_F3; break; + case NzKeyboard::F4: keysym = XK_F4; break; + case NzKeyboard::F5: keysym = XK_F5; break; + case NzKeyboard::F6: keysym = XK_F6; break; + case NzKeyboard::F7: keysym = XK_F7; break; + case NzKeyboard::F8: keysym = XK_F8; break; + case NzKeyboard::F9: keysym = XK_F9; break; + case NzKeyboard::F10: keysym = XK_F10; break; + case NzKeyboard::F11: keysym = XK_F11; break; + case NzKeyboard::F12: keysym = XK_F12; break; + case NzKeyboard::F13: keysym = XK_F13; break; + case NzKeyboard::F14: keysym = XK_F14; break; + case NzKeyboard::F15: keysym = XK_F15; break; + + // Flèches directionnelles + case NzKeyboard::Down: keysym = XK_Down; break; + case NzKeyboard::Left: keysym = XK_Left; break; + case NzKeyboard::Right: keysym = XK_Right; break; + case NzKeyboard::Up: keysym = XK_Up; break; + + // Pavé numérique + case NzKeyboard::Add: keysym = XK_KP_Add; break; + case NzKeyboard::Decimal: keysym = XK_KP_Decimal; break; + case NzKeyboard::Divide: keysym = XK_KP_Divide; break; + case NzKeyboard::Multiply: keysym = XK_KP_Multiply; break; + case NzKeyboard::Numpad0: keysym = XK_KP_0; break; + case NzKeyboard::Numpad1: keysym = XK_KP_1; break; + case NzKeyboard::Numpad2: keysym = XK_KP_2; break; + case NzKeyboard::Numpad3: keysym = XK_KP_3; break; + case NzKeyboard::Numpad4: keysym = XK_KP_4; break; + case NzKeyboard::Numpad5: keysym = XK_KP_5; break; + case NzKeyboard::Numpad6: keysym = XK_KP_6; break; + case NzKeyboard::Numpad7: keysym = XK_KP_7; break; + case NzKeyboard::Numpad8: keysym = XK_KP_8; break; + case NzKeyboard::Numpad9: keysym = XK_KP_9; break; + case NzKeyboard::Subtract: keysym = XK_KP_Subtract; break; + + // Divers + case NzKeyboard::Backslash: keysym = XK_backslash; break; + case NzKeyboard::Backspace: keysym = XK_BackSpace; break; + case NzKeyboard::Clear: keysym = XK_Clear; break; + case NzKeyboard::Comma: keysym = XK_comma; break; + case NzKeyboard::Dash: keysym = XK_minus; break; + case NzKeyboard::Delete: keysym = XK_Delete; break; + case NzKeyboard::End: keysym = XK_End; break; + case NzKeyboard::Equal: keysym = XK_equal; break; + case NzKeyboard::Escape: keysym = XK_Escape; break; + case NzKeyboard::Home: keysym = XK_Home; break; + case NzKeyboard::Insert: keysym = XK_Insert; break; + case NzKeyboard::LAlt: keysym = XK_Alt_L; break; + case NzKeyboard::LBracket: keysym = XK_bracketleft; break; + case NzKeyboard::LControl: keysym = XK_Control_L; break; + case NzKeyboard::LShift: keysym = XK_Shift_L; break; + case NzKeyboard::LSystem: keysym = XK_Super_L; break; + case NzKeyboard::Num0: keysym = XK_0; break; + case NzKeyboard::Num1: keysym = XK_1; break; + case NzKeyboard::Num2: keysym = XK_2; break; + case NzKeyboard::Num3: keysym = XK_3; break; + case NzKeyboard::Num4: keysym = XK_4; break; + case NzKeyboard::Num5: keysym = XK_5; break; + case NzKeyboard::Num6: keysym = XK_6; break; + case NzKeyboard::Num7: keysym = XK_7; break; + case NzKeyboard::Num8: keysym = XK_8; break; + case NzKeyboard::Num9: keysym = XK_9; break; + case NzKeyboard::PageDown: keysym = XK_Page_Down; break; + case NzKeyboard::PageUp: keysym = XK_Page_Up; break; + case NzKeyboard::Pause: keysym = XK_Pause; break; + case NzKeyboard::Period: keysym = XK_period; break; + case NzKeyboard::Print: keysym = XK_Print; break; + case NzKeyboard::PrintScreen: keysym = XK_Sys_Req; break; + case NzKeyboard::Quote: keysym = XK_quotedbl; break; + case NzKeyboard::RAlt: keysym = XK_Alt_R; break; + case NzKeyboard::RBracket: keysym = XK_bracketright; break; + case NzKeyboard::RControl: keysym = XK_Control_R; break; + case NzKeyboard::Return: keysym = XK_Return; break; + case NzKeyboard::RShift: keysym = XK_Shift_R; break; + case NzKeyboard::RSystem: keysym = XK_Super_R; break; + case NzKeyboard::Semicolon: keysym = XK_semicolon; break; + case NzKeyboard::Slash: keysym = XK_slash; break; + case NzKeyboard::Space: keysym = XK_space; break; + case NzKeyboard::Tab: keysym = XK_Tab; break; + case NzKeyboard::Tilde: keysym = XK_grave; break; + + // Touches navigateur + case NzKeyboard::Browser_Back: keysym = XF86XK_Back; break; + case NzKeyboard::Browser_Favorites: keysym = XF86XK_Favorites; break; + case NzKeyboard::Browser_Forward: keysym = XF86XK_Forward; break; + case NzKeyboard::Browser_Home: keysym = XF86XK_HomePage; break; + case NzKeyboard::Browser_Refresh: keysym = XF86XK_Refresh; break; + case NzKeyboard::Browser_Search: keysym = XF86XK_Search; break; + case NzKeyboard::Browser_Stop: keysym = XF86XK_Stop; break; + + // Touches de contrôle + case NzKeyboard::Media_Next: keysym = XF86XK_AudioNext; break; + case NzKeyboard::Media_Play: keysym = XF86XK_AudioPlay; break; + case NzKeyboard::Media_Previous: keysym = XF86XK_AudioPrev; break; + case NzKeyboard::Media_Stop: keysym = XF86XK_AudioStop; break; + + // Touches de contrôle du volume + case NzKeyboard::Volume_Down: keysym = XF86XK_AudioLowerVolume; break; + case NzKeyboard::Volume_Mute: keysym = XF86XK_AudioMute; break; + case NzKeyboard::Volume_Up: keysym = XF86XK_AudioRaiseVolume; break; + + // Touches à verrouillage + case NzKeyboard::CapsLock: keysym = XK_Caps_Lock; break; + case NzKeyboard::NumLock: keysym = XK_Num_Lock; break; + case NzKeyboard::ScrollLock: keysym = XK_Scroll_Lock; break; + + default: break; + } + + // Sanity checks + if (key < 0 || key >= NzKeyboard::Count || keysym == 0) + NazaraWarning("Key " + NzString::Number(key) + " is not handled in NzKeyboard"); + + return keysym; + } +} + +NzString NzEventImpl::GetKeyName(NzKeyboard::Key key) +{ + KeySym keySym = GetKeySym(key); + + // XKeysymToString returns a static area. + return XKeysymToString(keySym); +} + +NzVector2i NzEventImpl::GetMousePosition() +{ + NzScopedXCBConnection connection; + + NzScopedXCB error(nullptr); + + NzScopedXCB pointer( + xcb_query_pointer_reply( + connection, + xcb_query_pointer( + connection, + X11::XCBDefaultRootWindow(connection) + ), + &error + ) + ); + + if (error) + { + NazaraError("Failed to query pointer"); + return NzVector2i(-1, -1); + } + + return NzVector2i(pointer->root_x, pointer->root_y); +} + +NzVector2i NzEventImpl::GetMousePosition(const NzWindow& relativeTo) +{ + NzWindowHandle handle = relativeTo.GetHandle(); + if (handle) + { + // Open a connection with the X server + NzScopedXCBConnection connection; + + NzScopedXCB error(nullptr); + + NzScopedXCB pointer( + xcb_query_pointer_reply( + connection, + xcb_query_pointer( + connection, + handle + ), + &error + ) + ); + + if (error) + { + NazaraError("Failed to query pointer"); + return NzVector2i(-1, -1); + } + + return NzVector2i(pointer->win_x, pointer->win_y); + } + else + { + NazaraError("No window handle"); + return NzVector2i(-1, -1); + } +} + +bool NzEventImpl::IsKeyPressed(NzKeyboard::Key key) +{ + NzScopedXCBConnection connection; + + xcb_keysym_t keySym = GetKeySym(key); + + xcb_key_symbols_t* keySymbols = X11::XCBKeySymbolsAlloc(connection); + if (!keySymbols) + { + NazaraError("Failed to alloc key symbols"); + return false; + } + + NzScopedXCB keyCode = xcb_key_symbols_get_keycode(keySymbols, keySym); + if (!keyCode) + { + NazaraError("Failed to get key code"); + return false; + } + X11::XCBKeySymbolsFree(keySymbols); + + NzScopedXCB error(nullptr); + + // Get the whole keyboard state + NzScopedXCB keymap( + xcb_query_keymap_reply( + connection, + xcb_query_keymap(connection), + &error + ) + ); + + if (error) + { + NazaraError("Failed to query keymap"); + return false; + } + + // Check our keycode + return (keymap->keys[*keyCode.get() / 8] & (1 << (*keyCode.get() % 8))) != 0; +} + +bool NzEventImpl::IsMouseButtonPressed(NzMouse::Button button) +{ + NzScopedXCBConnection connection; + + NzScopedXCB error(nullptr); + + // Get pointer mask + NzScopedXCB pointer( + xcb_query_pointer_reply( + connection, + xcb_query_pointer( + connection, + X11::XCBDefaultRootWindow(connection) + ), + &error + ) + ); + + if (error) + { + NazaraError("Failed to query pointer"); + return false; + } + + uint16_t buttons = pointer->mask; + + switch (button) + { + case NzMouse::Left: return buttons & XCB_BUTTON_MASK_1; + case NzMouse::Right: return buttons & XCB_BUTTON_MASK_3; + case NzMouse::Middle: return buttons & XCB_BUTTON_MASK_2; + case NzMouse::XButton1: return false; // not supported by X + case NzMouse::XButton2: return false; // not supported by X + } + + NazaraError("Mouse button not supported."); + return false; +} + +void NzEventImpl::SetMousePosition(int x, int y) +{ + NzScopedXCBConnection connection; + + xcb_window_t root = X11::XCBDefaultRootWindow(connection); + + if (!X11::CheckCookie( + connection, + xcb_warp_pointer( + connection, + None, // Source window + root, // Destination window + 0, 0, // Source position + 0, 0, // Source size + x, y // Destination position + )) + ) + NazaraError("Failed to set mouse position"); + + xcb_flush(connection); +} + +void NzEventImpl::SetMousePosition(int x, int y, const NzWindow& relativeTo) +{ + NzScopedXCBConnection connection; + + NzWindowHandle handle = relativeTo.GetHandle(); + if (handle) + { + if (!X11::CheckCookie( + connection, + xcb_warp_pointer( + connection, + None, // Source window + handle, // Destination window + 0, 0, // Source position + 0, 0, // Source size + x, y // Destination position + )) + ) + NazaraError("Failed to set mouse position relative to window"); + + xcb_flush(connection); + } + else + NazaraError("No window handle"); +} diff --git a/src/Nazara/Utility/X11/InputImpl.hpp b/src/Nazara/Utility/X11/InputImpl.hpp new file mode 100644 index 000000000..e99726740 --- /dev/null +++ b/src/Nazara/Utility/X11/InputImpl.hpp @@ -0,0 +1,27 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_INPUTIMPL_HPP +#define NAZARA_INPUTIMPL_HPP + +#include +#include +#include +#include + +class NzEventImpl +{ + public: + static NzString GetKeyName(NzKeyboard::Key key); + static NzVector2i GetMousePosition(); + static NzVector2i GetMousePosition(const NzWindow& relativeTo); + static bool IsKeyPressed(NzKeyboard::Key key); + static bool IsMouseButtonPressed(NzMouse::Button button); + static void SetMousePosition(int x, int y); + static void SetMousePosition(int x, int y, const NzWindow& relativeTo); +}; + +#endif // NAZARA_INPUTIMPL_HPP diff --git a/src/Nazara/Utility/X11/ScopedXCB.cpp b/src/Nazara/Utility/X11/ScopedXCB.cpp new file mode 100644 index 000000000..296213e79 --- /dev/null +++ b/src/Nazara/Utility/X11/ScopedXCB.cpp @@ -0,0 +1,196 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include + +/*********************************************** + NzScopedXCBConnection +***********************************************/ + +NzScopedXCBConnection::NzScopedXCBConnection() : +m_connection(nullptr) +{ + m_connection = X11::OpenConnection(); +} + +NzScopedXCBConnection::~NzScopedXCBConnection() +{ + X11::CloseConnection(m_connection); +} + +NzScopedXCBConnection::operator xcb_connection_t*() const +{ + return m_connection; +} + +/*********************************************** + NzScopedXCBEWMHConnection +***********************************************/ + +NzScopedXCBEWMHConnection::NzScopedXCBEWMHConnection(xcb_connection_t* connection) : +m_ewmhConnection(nullptr) +{ + m_ewmhConnection = X11::OpenEWMHConnection(connection); +} + +NzScopedXCBEWMHConnection::~NzScopedXCBEWMHConnection() +{ + X11::CloseEWMHConnection(m_ewmhConnection); +} + +xcb_ewmh_connection_t* NzScopedXCBEWMHConnection::operator ->() const +{ + return m_ewmhConnection; +} + +NzScopedXCBEWMHConnection::operator xcb_ewmh_connection_t*() const +{ + return m_ewmhConnection; +} + +/*********************************************** + NzXCBGContext +***********************************************/ + +NzXCBGContext::NzXCBGContext(xcb_connection_t* connection) : +m_connection(connection), +m_gcontext(XCB_NONE) +{ + NazaraAssert(connection, "Connection must have been established"); +} + +NzXCBGContext::~NzXCBGContext() +{ + Destroy(); +} + +bool NzXCBGContext::Create(xcb_drawable_t drawable, uint32_t value_mask, const uint32_t* value_list) +{ + NazaraAssert(m_gcontext == XCB_NONE, "Context must have been destroyed before or just created"); + + m_gcontext = xcb_generate_id(m_connection); + + return X11::CheckCookie( + m_connection, + xcb_create_gc( + m_connection, + m_gcontext, + drawable, + value_mask, + value_list + )); +} + +void NzXCBGContext::Destroy() +{ + if (m_gcontext == XCB_NONE) + return; + + if (!X11::CheckCookie( + m_connection, + xcb_free_gc( + m_connection, + m_gcontext + )) + ) + NazaraError("Failed to free gcontext"); + + m_gcontext = XCB_NONE; +} + +NzXCBGContext::operator xcb_gcontext_t() const +{ + return m_gcontext; +} + +/*********************************************** + NzXCBPixmap +***********************************************/ + +NzXCBPixmap::NzXCBPixmap() : +m_connection(nullptr), +m_pixmap(XCB_NONE) +{ +} + +NzXCBPixmap::NzXCBPixmap(xcb_connection_t* connection) : +m_connection(connection), +m_pixmap(XCB_NONE) +{ +} + +NzXCBPixmap::~NzXCBPixmap() +{ + Destroy(); +} + +void NzXCBPixmap::Connect(xcb_connection_t* connection) +{ + NazaraAssert(connection && !m_connection, "Connection must be established"); + + m_connection = connection; +} + +bool NzXCBPixmap::Create(uint8_t depth, xcb_drawable_t drawable, uint16_t width, uint16_t height) +{ + NazaraAssert(m_pixmap == XCB_NONE, "Pixmap must have been destroyed before or just created"); + + m_pixmap = xcb_generate_id(m_connection); + + return X11::CheckCookie( + m_connection, + xcb_create_pixmap( + m_connection, + depth, + m_pixmap, + drawable, + width, + height + )); +} + +bool NzXCBPixmap::CreatePixmapFromBitmapData(xcb_drawable_t drawable, uint8_t* data, uint32_t width, uint32_t height, uint32_t depth, uint32_t fg, uint32_t bg, xcb_gcontext_t* gcp) +{ + NazaraAssert(m_pixmap == XCB_NONE, "Pixmap must have been destroyed before or just created"); + + m_pixmap = xcb_create_pixmap_from_bitmap_data( + m_connection, + drawable, + data, + width, + height, + depth, + fg, + bg, + gcp + ); + + return m_pixmap != XCB_NONE; +} + +void NzXCBPixmap::Destroy() +{ + if (m_pixmap == XCB_NONE) + return; + + if (!X11::CheckCookie( + m_connection, + xcb_free_pixmap( + m_connection, + m_pixmap + )) + ) + NazaraError("Failed to free pixmap"); + + m_pixmap = XCB_NONE; +} + +NzXCBPixmap::operator xcb_pixmap_t() const +{ + return m_pixmap; +} diff --git a/src/Nazara/Utility/X11/ScopedXCB.hpp b/src/Nazara/Utility/X11/ScopedXCB.hpp new file mode 100644 index 000000000..35ef9009b --- /dev/null +++ b/src/Nazara/Utility/X11/ScopedXCB.hpp @@ -0,0 +1,95 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_SCOPEDXCB_HPP +#define NAZARA_SCOPEDXCB_HPP + +#include + +class NzScopedXCBConnection +{ + public: + NzScopedXCBConnection(); + ~NzScopedXCBConnection(); + + operator xcb_connection_t*() const; + + private: + xcb_connection_t* m_connection; +}; + +class NzScopedXCBEWMHConnection +{ + public: + NzScopedXCBEWMHConnection(xcb_connection_t* connection); + ~NzScopedXCBEWMHConnection(); + + xcb_ewmh_connection_t* operator ->() const; + + operator xcb_ewmh_connection_t*() const; + + private: + xcb_ewmh_connection_t* m_ewmhConnection; +}; + +template +class NzScopedXCB +{ + public: + NzScopedXCB(T* pointer); + ~NzScopedXCB(); + + T* operator ->() const; + T** operator &(); + + operator bool() const; + + T* get() const; + + private: + T* m_pointer; +}; + +class NzXCBGContext +{ + public: + NzXCBGContext(xcb_connection_t* connection); + ~NzXCBGContext(); + + bool Create(xcb_drawable_t drawable, uint32_t value_mask, const uint32_t* value_list); + + void Destroy(); + + operator xcb_gcontext_t() const; + + private: + xcb_connection_t* m_connection; + xcb_gcontext_t m_gcontext; +}; + +class NzXCBPixmap +{ + public: + NzXCBPixmap(); + NzXCBPixmap(xcb_connection_t* connection); + ~NzXCBPixmap(); + + void Connect(xcb_connection_t* connection); + bool Create(uint8_t depth, xcb_drawable_t drawable, uint16_t width, uint16_t height); + bool CreatePixmapFromBitmapData(xcb_drawable_t drawable, uint8_t* data, uint32_t width, uint32_t height, uint32_t depth, uint32_t fg, uint32_t bg, xcb_gcontext_t* gcp); + + void Destroy(); + + operator xcb_pixmap_t() const; + + private: + xcb_connection_t* m_connection; + xcb_pixmap_t m_pixmap; +}; + +#include + +#endif // NAZARA_SCOPEDXCB_HPP diff --git a/src/Nazara/Utility/X11/ScopedXCB.inl b/src/Nazara/Utility/X11/ScopedXCB.inl new file mode 100644 index 000000000..b5bc7cb71 --- /dev/null +++ b/src/Nazara/Utility/X11/ScopedXCB.inl @@ -0,0 +1,44 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +template +NzScopedXCB::NzScopedXCB(T* pointer) : +m_pointer(pointer) +{ +} + +template +NzScopedXCB::~NzScopedXCB() +{ + std::free(m_pointer); +} + +template +T* NzScopedXCB::operator ->() const +{ + return m_pointer; +} + +template +T** NzScopedXCB::operator &() +{ + return &m_pointer; +} + +template +NzScopedXCB::operator bool() const +{ + return m_pointer != nullptr; +} + +template +T* NzScopedXCB::get() const +{ + return m_pointer; +} + +#include diff --git a/src/Nazara/Utility/X11/VideoModeImpl.cpp b/src/Nazara/Utility/X11/VideoModeImpl.cpp new file mode 100644 index 000000000..dcdec7f97 --- /dev/null +++ b/src/Nazara/Utility/X11/VideoModeImpl.cpp @@ -0,0 +1,166 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include + +NzVideoMode NzVideoModeImpl::GetDesktopMode() +{ + NzVideoMode desktopMode; + + NzScopedXCBConnection connection; + + // Retrieve the default screen + xcb_screen_t* screen = X11::XCBDefaultScreen(connection); + + NzScopedXCB error(nullptr); + + // 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) + { + // Randr extension is not supported: we cannot get the video modes + NazaraError("Failed to use the RandR extension while trying to get the desktop video mode"); + return desktopMode; + } + + // Load RandR and check its version + NzScopedXCB randrVersion(xcb_randr_query_version_reply( + connection, + xcb_randr_query_version( + connection, + 1, + 1 + ), + &error + )); + + if (error) + { + NazaraError("Failed to load the RandR extension while trying to get the desktop video mode"); + return desktopMode; + } + + // Get the current configuration + NzScopedXCB 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 + NazaraError("Failed to retrieve the screen configuration while trying to get the desktop video mode"); + 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 = NzVideoMode(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 + { + NazaraError("Failed to retrieve any screen sizes while trying to get the desktop video mode"); + } + + return desktopMode; +} + +void NzVideoModeImpl::GetFullscreenModes(std::vector& modes) +{ + NzScopedXCBConnection connection; + + // Retrieve the default screen + xcb_screen_t* screen = X11::XCBDefaultScreen(connection); + + NzScopedXCB error(nullptr); + + const xcb_query_extension_reply_t* randrExt = xcb_get_extension_data(connection, &xcb_randr_id); + + if (!randrExt || !randrExt->present) + { + // Randr extension is not supported: we cannot get the video modes + NazaraError("Failed to use the RandR extension while trying to get the supported video modes"); + return; + } + + // Load RandR and check its version + NzScopedXCB randrVersion(xcb_randr_query_version_reply( + connection, + xcb_randr_query_version( + connection, + 1, + 1 + ), + &error + )); + + if (error) + { + NazaraError("Failed to load the RandR extension while trying to get the supported video modes"); + return; + } + + // Get the current configuration + NzScopedXCB 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 + NazaraError("Failed to retrieve the screen configuration while trying to get the supported video modes"); + return; + } + + // Get the available screen sizes + xcb_randr_screen_size_t* sizes = xcb_randr_get_screen_info_sizes(config.get()); + if (sizes && (config->nSizes > 0)) + { + // 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 + NzVideoMode 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); + } + } + } +} diff --git a/src/Nazara/Utility/X11/VideoModeImpl.hpp b/src/Nazara/Utility/X11/VideoModeImpl.hpp new file mode 100644 index 000000000..3a53f5e94 --- /dev/null +++ b/src/Nazara/Utility/X11/VideoModeImpl.hpp @@ -0,0 +1,19 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_VIDEOMODEIMPL_HPP +#define NAZARA_VIDEOMODEIMPL_HPP + +#include + +class NzVideoModeImpl +{ + public: + static NzVideoMode GetDesktopMode(); + static void GetFullscreenModes(std::vector& modes); +}; + +#endif // NNAZARA_VIDEOMODEIMPL_HPP diff --git a/src/Nazara/Utility/X11/WindowImpl.cpp b/src/Nazara/Utility/X11/WindowImpl.cpp new file mode 100644 index 000000000..4087ba58d --- /dev/null +++ b/src/Nazara/Utility/X11/WindowImpl.cpp @@ -0,0 +1,1577 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +// Un grand merci à Laurent Gomila pour la SFML qui m'aura bien aidé à réaliser cette implémentation + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + Icon working sometimes + EnableKeyRepeat (Working but is it the right behaviour ?) + Fullscreen (No alt + tab) + Smooth scroll (???) + Thread (Not tested a lot) + Event listener // ? + Cleanup // Done ? +*/ + +namespace +{ + NzWindowImpl* fullscreenWindow = nullptr; + + const uint32_t 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; + + xcb_cursor_t hiddenCursor = 0; + + xcb_connection_t* connection = nullptr; + + void CreateHiddenCursor() + { + NzXCBPixmap cursorPixmap(connection); + + xcb_window_t window = X11::XCBDefaultRootWindow(connection); + + if (!cursorPixmap.Create( + 1, + window, + 1, + 1 + )) + { + NazaraError("Failed to create pixmap for hidden cursor"); + return; + } + + hiddenCursor = xcb_generate_id(connection); + + // Create the cursor, using the pixmap as both the shape and the mask of the cursor + if (!X11::CheckCookie( + connection, + xcb_create_cursor( + connection, + hiddenCursor, + cursorPixmap, + cursorPixmap, + 0, 0, 0, // Foreground RGB color + 0, 0, 0, // Background RGB color + 0, // X + 0 // Y + )) + ) + NazaraError("Failed to create hidden cursor"); + } +} + +NzWindowImpl::NzWindowImpl(NzWindow* parent) : +m_window(0), +m_style(0), +m_parent(parent), +m_smoothScrolling(false), +m_scrolling(0), +m_mousePos(0, 0), +m_keyRepeat(true) +{ + std::memset(&m_size_hints, 0, sizeof(m_size_hints)); +} + +NzWindowImpl::~NzWindowImpl() +{ + // Cleanup graphical resources + CleanUp(); + + // We clean up the event queue + UpdateEventQueue(nullptr); + UpdateEventQueue(nullptr); +} + +bool NzWindowImpl::Create(const NzVideoMode& mode, const NzString& title, nzUInt32 style) +{ + bool fullscreen = (style & nzWindowStyle_Fullscreen) != 0; + m_eventListener = true; + m_ownsWindow = true; + m_style = style; + + std::memset(&m_oldVideoMode, 0, sizeof(m_oldVideoMode)); + + m_screen = X11::XCBDefaultScreen(connection); + + // Compute position and size + int left = fullscreen ? 0 : (m_screen->width_in_pixels - mode.width) / 2; + int top = fullscreen ? 0 : (m_screen->height_in_pixels - mode.height) / 2; + int width = mode.width; + int height = mode.height; + + // Define the window attributes + xcb_colormap_t colormap = xcb_generate_id(connection); + xcb_create_colormap(connection, XCB_COLORMAP_ALLOC_NONE, colormap, m_screen->root, m_screen->root_visual); + const uint32_t value_list[] = { fullscreen, eventMask, colormap }; + + NzCallOnExit onExit([&](){ + if (!X11::CheckCookie( + connection, + xcb_free_colormap( + connection, + colormap + )) + ) + NazaraError("Failed to free colormap"); + }); + + // Create the window + m_window = xcb_generate_id(connection); + + if (!X11::CheckCookie( + connection, + xcb_create_window_checked( + connection, + XCB_COPY_FROM_PARENT, + m_window, + m_screen->root, + left, top, + width, height, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + m_screen->root_visual, + XCB_CW_EVENT_MASK | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_COLORMAP, + value_list + ))) + { + NazaraError("Failed to create window"); + return false; + } + + // Flush the commands queue + xcb_flush(connection); + + // We get default normal hints for the new window + NzScopedXCB error(nullptr); + xcb_icccm_get_wm_normal_hints_reply( + connection, + xcb_icccm_get_wm_normal_hints( + connection, + m_window), + &m_size_hints, + &error + ); + if (error) + NazaraError("Failed to get size hints"); + + // And we modify the size and the position. + xcb_icccm_size_hints_set_position(&m_size_hints, false, left, top); + xcb_icccm_size_hints_set_size(&m_size_hints, false, width, height); + if (!UpdateNormalHints()) + NazaraError("Failed to set window configuration"); + + // Do some common initializations + CommonInitialize(); + + if (!(m_style & nzWindowStyle_Fullscreen)) + SetMotifHints(); + + // Flush the commands queue + xcb_flush(connection); + + // Set the window's name + SetTitle(title); + + #if NAZARA_UTILITY_THREADED_WINDOW + NzMutex mutex; + NzConditionVariable condition; + m_threadActive = true; + + // We wait that thread is well launched + mutex.Lock(); + m_thread = NzThread(WindowThread, this, &mutex, &condition); + condition.Wait(&mutex); + mutex.Unlock(); + #endif + + // Set fullscreen video mode and switch to fullscreen if necessary + if (fullscreen) + { + SetPosition(0, 0); + SetVideoMode(mode); + SwitchToFullscreen(); + } + + return true; +} + +bool NzWindowImpl::Create(NzWindowHandle handle) +{ + std::memset(&m_oldVideoMode, 0, sizeof(m_oldVideoMode)); + + m_screen = X11::XCBDefaultScreen(connection); + + if (!handle) + { + NazaraError("Invalid handle"); + return false; + } + + // Save the window handle + m_window = handle; + + m_ownsWindow = false; + m_eventListener = false; + + NzScopedXCB error(nullptr); + + // We try to get informations from the shared window. + xcb_icccm_get_wm_normal_hints_reply( + connection, + xcb_icccm_get_wm_normal_hints( + connection, + m_window), + &m_size_hints, + &error + ); + + if (error) + { + NazaraError("Failed to obtain sizes and positions"); + return false; + } + + // Do some common initializations + CommonInitialize(); + + // Flush the commands queue + xcb_flush(connection); + + return true; +} + +void NzWindowImpl::Destroy() +{ + if (m_ownsWindow) + { + #if NAZARA_UTILITY_THREADED_WINDOW + if (m_thread.IsJoinable()) + { + m_threadActive = false; + m_thread.Join(); + } + #else + // Destroy the window + if (m_window && m_ownsWindow) + { + // Unhide the mouse cursor (in case it was hidden) + SetCursor(nzWindowCursor_Default); + + if (!X11::CheckCookie( + connection, + xcb_destroy_window( + connection, + m_window + )) + ) + NazaraError("Failed to destroy window"); + + xcb_flush(connection); + } + #endif + } + else + SetEventListener(false); +} + +void NzWindowImpl::EnableKeyRepeat(bool enable) +{ + m_keyRepeat = enable; +} + +void NzWindowImpl::EnableSmoothScrolling(bool enable) +{ + m_smoothScrolling = enable; +} + +NzWindowHandle NzWindowImpl::GetHandle() const +{ + return m_window; +} + +unsigned int NzWindowImpl::GetHeight() const +{ + return m_size_hints.height; +} + +NzVector2i NzWindowImpl::GetPosition() const +{ + return { m_size_hints.x, m_size_hints.y }; +} + +NzVector2ui NzWindowImpl::GetSize() const +{ + return NzVector2ui(m_size_hints.width, m_size_hints.height); +} + +nzUInt32 NzWindowImpl::GetStyle() const +{ + return m_style; +} + +NzString NzWindowImpl::GetTitle() const +{ + NzScopedXCBEWMHConnection ewmhConnection(connection); + + NzScopedXCB error(nullptr); + + xcb_ewmh_get_utf8_strings_reply_t data; + xcb_ewmh_get_wm_name_reply(ewmhConnection, + xcb_ewmh_get_wm_name(ewmhConnection, m_window), &data, &error); + + if (error) + NazaraError("Failed to get window's title"); + + NzString tmp(data.strings, data.strings_len); + + xcb_ewmh_get_utf8_strings_reply_wipe(&data); + + return tmp; +} + +unsigned int NzWindowImpl::GetWidth() const +{ + return m_size_hints.width; +} + +bool NzWindowImpl::HasFocus() const +{ + NzScopedXCB error(nullptr); + + NzScopedXCB reply(xcb_get_input_focus_reply( + connection, + xcb_get_input_focus_unchecked( + connection + ), + &error + )); + + if (error) + NazaraError("Failed to check if window has focus"); + + return (reply->focus == m_window); +} + +void NzWindowImpl::IgnoreNextMouseEvent(int mouseX, int mouseY) +{ + // Petite astuce ... + m_mousePos.x = mouseX; + m_mousePos.y = mouseY; +} + +bool NzWindowImpl::IsMinimized() const +{ + NzScopedXCBEWMHConnection ewmhConnection(connection); + + NzScopedXCB error(nullptr); + bool isMinimized = false; + + xcb_ewmh_get_atoms_reply_t atomReply; + if (xcb_ewmh_get_wm_state_reply(ewmhConnection, + xcb_ewmh_get_wm_state(ewmhConnection, m_window), &atomReply, &error) == 1) + { + for (unsigned int i = 0; i < atomReply.atoms_len; i++) + if (atomReply.atoms[i] == ewmhConnection->_NET_WM_STATE_HIDDEN) + isMinimized = true; + + xcb_ewmh_get_atoms_reply_wipe(&atomReply); + } + + if (error) + NazaraError("Failed to determine if window is minimized"); + + return isMinimized; +} + +bool NzWindowImpl::IsVisible() const +{ + return !IsMinimized(); // Visibility event ? +} + +void NzWindowImpl::ProcessEvents(bool block) +{ + if (m_ownsWindow) + { + xcb_generic_event_t* event = nullptr; + + if (block) + { + event = xcb_wait_for_event(connection); + if (event) + { + UpdateEventQueue(event); + ProcessEvent(event); + } + } + else + { + event = xcb_poll_for_event(connection); + while (event) + { + UpdateEventQueue(event); + xcb_generic_event_t* tmp = xcb_poll_for_event(connection); + UpdateEventQueue(tmp); + ProcessEvent(event); + if (tmp) + ProcessEvent(tmp); + event = xcb_poll_for_event(connection); + } + } + } +} + +void NzWindowImpl::SetCursor(nzWindowCursor windowCursor) +{ + if (windowCursor == nzWindowCursor_None) + SetCursor(hiddenCursor); + else + { + const char* name = ConvertWindowCursorToXName(windowCursor); + + xcb_cursor_context_t* ctx; + if (xcb_cursor_context_new(connection, m_screen, &ctx) >= 0) + { + xcb_cursor_t cursor = xcb_cursor_load_cursor(ctx, name); + SetCursor(cursor); + xcb_free_cursor(connection, cursor); + xcb_cursor_context_free(ctx); + } + } +} + +void NzWindowImpl::SetCursor(const NzCursor& cursor) +{ + if (!cursor.IsValid()) + { + NazaraError("Cursor is not valid"); + return; + } + + SetCursor(cursor.m_impl->GetCursor()); +} + +void NzWindowImpl::SetEventListener(bool listener) +{ + if (m_ownsWindow) + m_eventListener = listener; + else if (listener != m_eventListener) + { + if (listener) + { + const uint32_t value_list[] = { eventMask }; + + if (!X11::CheckCookie( + connection, + xcb_change_window_attributes( + connection, + m_window, + XCB_CW_EVENT_MASK, + value_list + )) + ) + NazaraError("Failed to change event for listener"); + + m_eventListener = true; + } + else if (m_eventListener) + { + const uint32_t value_list[] = { XCB_EVENT_MASK_NO_EVENT }; + + if (!X11::CheckCookie( + connection, + xcb_change_window_attributes( + connection, + m_window, + XCB_CW_EVENT_MASK, + value_list + )) + ) + NazaraError("Failed to change event for listener"); + + m_eventListener = false; + } + } +} + +void NzWindowImpl::SetFocus() +{ + if (!X11::CheckCookie( + connection, + xcb_set_input_focus( + connection, + XCB_INPUT_FOCUS_POINTER_ROOT, + m_window, + XCB_CURRENT_TIME + )) + ) + NazaraError("Failed to set input focus"); + + const uint32_t values[] = { XCB_STACK_MODE_ABOVE }; + + if (!X11::CheckCookie( + connection, + xcb_configure_window( + connection, + m_window, + XCB_CONFIG_WINDOW_STACK_MODE, + values + )) + ) + NazaraError("Failed to set focus"); +} + +void NzWindowImpl::SetIcon(const NzIcon& icon) +{ + if (!icon.IsValid()) + { + NazaraError("Icon is not valid"); + return; + } + + xcb_pixmap_t icon_pixmap = icon.m_impl->GetIcon(); + xcb_pixmap_t mask_pixmap = icon.m_impl->GetMask(); + + NzScopedXCB error(nullptr); + + xcb_icccm_wm_hints_t hints; + std::memset(&hints, 0, sizeof(hints)); + + xcb_icccm_get_wm_hints_reply( + connection, + xcb_icccm_get_wm_hints( + connection, + m_window), + &hints, + &error + ); + + if (error) + NazaraError("Failed to get wm hints"); + + xcb_icccm_wm_hints_set_icon_pixmap(&hints, icon_pixmap); + xcb_icccm_wm_hints_set_icon_mask(&hints, mask_pixmap); + + if (!X11::CheckCookie( + connection, + xcb_icccm_set_wm_hints( + connection, + m_window, + &hints + )) + ) + NazaraError("Failed to set wm hints"); + + xcb_flush(connection); +} + +void NzWindowImpl::SetMaximumSize(int width, int height) +{ + if (width < 0) + width = m_screen->width_in_pixels; + if (height < 0) + height = m_screen->height_in_pixels; + + xcb_icccm_size_hints_set_max_size(&m_size_hints, width, height); + if (!UpdateNormalHints()) + NazaraError("Failed to set maximum size"); + + xcb_flush(connection); +} + +void NzWindowImpl::SetMinimumSize(int width, int height) +{ + xcb_icccm_size_hints_set_min_size(&m_size_hints, width, height); + if (!UpdateNormalHints()) + NazaraError("Failed to set minimum size"); + + xcb_flush(connection); +} + +void NzWindowImpl::SetPosition(int x, int y) +{ + xcb_icccm_size_hints_set_position(&m_size_hints, true, x, y); + if (!UpdateNormalHints()) + NazaraError("Failed to set size hints position"); + + const uint32_t values[] = { static_cast(x), static_cast(y) }; + if (!X11::CheckCookie( + connection, + xcb_configure_window( + connection, + m_window, + XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, + values + )) + ) + NazaraError("Failed to set position"); + + xcb_flush(connection); +} + +void NzWindowImpl::SetSize(unsigned int width, unsigned int height) +{ + xcb_icccm_size_hints_set_size(&m_size_hints, true, width, height); + if (!UpdateNormalHints()) + NazaraError("Failed to set size hints sizes"); + + const uint32_t values[] = { width, height }; + if (!X11::CheckCookie( + connection, + xcb_configure_window( + connection, + m_window, + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, + values + )) + ) + NazaraError("Failed to set sizes"); + + xcb_flush(connection); +} + +void NzWindowImpl::SetStayOnTop(bool stayOnTop) +{ + NzScopedXCBEWMHConnection ewmhConnection(connection); + + xcb_atom_t onTop; // It is not really working + if (stayOnTop) + onTop = ewmhConnection->_NET_WM_STATE_ABOVE; + else + onTop = ewmhConnection->_NET_WM_STATE_BELOW; + + if (!X11::CheckCookie( + connection, + xcb_ewmh_set_wm_state( + ewmhConnection, + m_window, + 1, + &onTop + )) + ) + NazaraError("Failed to set stay on top"); + + xcb_flush(connection); +} + +void NzWindowImpl::SetTitle(const NzString& title) +{ + NzScopedXCBEWMHConnection ewmhConnection(connection); + + if (!X11::CheckCookie( + connection, + xcb_ewmh_set_wm_name( + ewmhConnection, + m_window, + title.GetSize(), + title.GetConstBuffer() + )) + ) + NazaraError("Failed to set title"); + + xcb_flush(connection); +} + +void NzWindowImpl::SetVisible(bool visible) +{ + if (visible) + { + if (!X11::CheckCookie( + connection, + xcb_map_window( + connection, + m_window + )) + ) + NazaraError("Failed to change window visibility to visible"); + } + else + { + if (!X11::CheckCookie( + connection, + xcb_unmap_window( + connection, + m_window + )) + ) + NazaraError("Failed to change window visibility to invisible"); + } + + xcb_flush(connection); +} + +bool NzWindowImpl::Initialize() +{ + X11::Initialize(); + + connection = X11::OpenConnection(); + + // Create the hidden cursor + CreateHiddenCursor(); + + return true; +} + +void NzWindowImpl::Uninitialize() +{ + // Destroy the cursor + if (hiddenCursor) + { + xcb_free_cursor(connection, hiddenCursor); + hiddenCursor = 0; + } + + X11::CloseConnection(connection); + + X11::Uninitialize(); +} + +void NzWindowImpl::CleanUp() +{ + // Restore the previous video mode (in case we were running in fullscreen) + ResetVideoMode(); +} + +xcb_keysym_t NzWindowImpl::ConvertKeyCodeToKeySym(xcb_keycode_t keycode, uint16_t state) +{ + xcb_key_symbols_t* keysyms = X11::XCBKeySymbolsAlloc(connection); + if (!keysyms) + { + NazaraError("Failed to get key symbols"); + return XCB_NONE; + } + + int col = state & XCB_MOD_MASK_SHIFT ? 1 : 0; + const int altGrOffset = 4; + if (state & XCB_MOD_MASK_5) + col += altGrOffset; + xcb_keysym_t keysym = xcb_key_symbols_get_keysym(keysyms, keycode, col); + if (keysym == XCB_NO_SYMBOL) + keysym = xcb_key_symbols_get_keysym(keysyms, keycode, col ^ 0x1); + X11::XCBKeySymbolsFree(keysyms); + + return keysym; +} + +NzKeyboard::Key NzWindowImpl::ConvertVirtualKey(xcb_keysym_t symbol) +{ + // First convert to uppercase (to avoid dealing with two different keysyms for the same key) + KeySym lower, key; + XConvertCase(symbol, &lower, &key); + + switch (key) + { + // Lettres + case XK_A: return NzKeyboard::A; + case XK_B: return NzKeyboard::B; + case XK_C: return NzKeyboard::C; + case XK_D: return NzKeyboard::D; + case XK_E: return NzKeyboard::E; + case XK_F: return NzKeyboard::F; + case XK_G: return NzKeyboard::G; + case XK_H: return NzKeyboard::H; + case XK_I: return NzKeyboard::I; + case XK_J: return NzKeyboard::J; + case XK_K: return NzKeyboard::K; + case XK_L: return NzKeyboard::L; + case XK_M: return NzKeyboard::M; + case XK_N: return NzKeyboard::N; + case XK_O: return NzKeyboard::O; + case XK_P: return NzKeyboard::P; + case XK_Q: return NzKeyboard::Q; + case XK_R: return NzKeyboard::R; + case XK_S: return NzKeyboard::S; + case XK_T: return NzKeyboard::T; + case XK_U: return NzKeyboard::U; + case XK_V: return NzKeyboard::V; + case XK_W: return NzKeyboard::W; + case XK_X: return NzKeyboard::X; + case XK_Y: return NzKeyboard::Y; + case XK_Z: return NzKeyboard::Z; + + // Touches de fonction + case XK_F1: return NzKeyboard::F1; + case XK_F2: return NzKeyboard::F2; + case XK_F3: return NzKeyboard::F3; + case XK_F4: return NzKeyboard::F4; + case XK_F5: return NzKeyboard::F5; + case XK_F6: return NzKeyboard::F6; + case XK_F7: return NzKeyboard::F7; + case XK_F8: return NzKeyboard::F8; + case XK_F9: return NzKeyboard::F9; + case XK_F10: return NzKeyboard::F10; + case XK_F11: return NzKeyboard::F11; + case XK_F12: return NzKeyboard::F12; + case XK_F13: return NzKeyboard::F13; + case XK_F14: return NzKeyboard::F14; + case XK_F15: return NzKeyboard::F15; + + // Flèches directionnelles + case XK_Down: return NzKeyboard::Down; + case XK_Left: return NzKeyboard::Left; + case XK_Right: return NzKeyboard::Right; + case XK_Up: return NzKeyboard::Up; + + // Pavé numérique + case XK_KP_Add: return NzKeyboard::Add; + case XK_KP_Decimal: return NzKeyboard::Decimal; + case XK_KP_Delete: return NzKeyboard::Decimal; + case XK_KP_Divide: return NzKeyboard::Divide; + case XK_KP_Multiply: return NzKeyboard::Multiply; + case XK_KP_Insert: return NzKeyboard::Numpad0; + case XK_KP_End: return NzKeyboard::Numpad1; + case XK_KP_Down: return NzKeyboard::Numpad2; + case XK_KP_Page_Down: return NzKeyboard::Numpad3; + case XK_KP_Left: return NzKeyboard::Numpad4; + case XK_KP_Begin: return NzKeyboard::Numpad5; + case XK_KP_Right: return NzKeyboard::Numpad6; + case XK_KP_Home: return NzKeyboard::Numpad7; + case XK_KP_Up: return NzKeyboard::Numpad8; + case XK_KP_Page_Up: return NzKeyboard::Numpad9; + case XK_KP_Enter: return NzKeyboard::Return; + case XK_KP_Subtract: return NzKeyboard::Subtract; + + // Divers + case XK_backslash: return NzKeyboard::Backslash; + case XK_BackSpace: return NzKeyboard::Backspace; + case XK_Clear: return NzKeyboard::Clear; + case XK_comma: return NzKeyboard::Comma; + case XK_minus: return NzKeyboard::Dash; + case XK_Delete: return NzKeyboard::Delete; + case XK_End: return NzKeyboard::End; + case XK_equal: return NzKeyboard::Equal; + case XK_Escape: return NzKeyboard::Escape; + case XK_Home: return NzKeyboard::Home; + case XK_Insert: return NzKeyboard::Insert; + case XK_Alt_L: return NzKeyboard::LAlt; + case XK_bracketleft: return NzKeyboard::LBracket; + case XK_Control_L: return NzKeyboard::LControl; + case XK_Shift_L: return NzKeyboard::LShift; + case XK_Super_L: return NzKeyboard::LSystem; + case XK_0: return NzKeyboard::Num0; + case XK_1: return NzKeyboard::Num1; + case XK_2: return NzKeyboard::Num2; + case XK_3: return NzKeyboard::Num3; + case XK_4: return NzKeyboard::Num4; + case XK_5: return NzKeyboard::Num5; + case XK_6: return NzKeyboard::Num6; + case XK_7: return NzKeyboard::Num7; + case XK_8: return NzKeyboard::Num8; + case XK_9: return NzKeyboard::Num9; + case XK_Page_Down: return NzKeyboard::PageDown; + case XK_Page_Up: return NzKeyboard::PageUp; + case XK_Pause: return NzKeyboard::Pause; + case XK_period: return NzKeyboard::Period; + case XK_Print: return NzKeyboard::Print; + case XK_Sys_Req: return NzKeyboard::PrintScreen; + case XK_quotedbl: return NzKeyboard::Quote; + case XK_Alt_R: return NzKeyboard::RAlt; + case XK_bracketright: return NzKeyboard::RBracket; + case XK_Control_R: return NzKeyboard::RControl; + case XK_Return: return NzKeyboard::Return; + case XK_Shift_R: return NzKeyboard::RShift; + case XK_Super_R: return NzKeyboard::RSystem; + case XK_semicolon: return NzKeyboard::Semicolon; + case XK_slash: return NzKeyboard::Slash; + case XK_space: return NzKeyboard::Space; + case XK_Tab: return NzKeyboard::Tab; + case XK_grave: return NzKeyboard::Tilde; + + // Touches navigateur + case XF86XK_Back: return NzKeyboard::Browser_Back; + case XF86XK_Favorites: return NzKeyboard::Browser_Favorites; + case XF86XK_Forward: return NzKeyboard::Browser_Forward; + case XF86XK_HomePage: return NzKeyboard::Browser_Home; + case XF86XK_Refresh: return NzKeyboard::Browser_Refresh; + case XF86XK_Search: return NzKeyboard::Browser_Search; + case XF86XK_Stop: return NzKeyboard::Browser_Stop; + + // Touches de contrôle + case XF86XK_AudioNext: return NzKeyboard::Media_Next; + case XF86XK_AudioPlay: return NzKeyboard::Media_Play; + case XF86XK_AudioPrev: return NzKeyboard::Media_Previous; + case XF86XK_AudioStop: return NzKeyboard::Media_Stop; + + // Touches de contrôle du volume + case XF86XK_AudioLowerVolume: return NzKeyboard::Volume_Down; + case XF86XK_AudioMute: return NzKeyboard::Volume_Mute; + case XF86XK_AudioRaiseVolume: return NzKeyboard::Volume_Up; + + // Touches à verrouillage + case XK_Caps_Lock: return NzKeyboard::CapsLock; + case XK_Num_Lock: return NzKeyboard::NumLock; + case XK_Scroll_Lock: return NzKeyboard::ScrollLock; + + default: + return NzKeyboard::Undefined; + } +} + +const char* NzWindowImpl::ConvertWindowCursorToXName(nzWindowCursor cursor) +{ + // http://gnome-look.org/content/preview.php?preview=1&id=128170&file1=128170-1.png&file2=&file3=&name=Dummy+X11+cursors&PHPSESSID=6 + switch (cursor) + { + case nzWindowCursor_Crosshair: + return "crosshair"; + case nzWindowCursor_Default: + return "left_ptr"; + case nzWindowCursor_Hand: + return "hand"; + case nzWindowCursor_Help: + return "help"; + case nzWindowCursor_Move: + return "fleur"; + case nzWindowCursor_None: + return "none"; // Handled in set cursor + case nzWindowCursor_Pointer: + return "hand"; + case nzWindowCursor_Progress: + return "watch"; + case nzWindowCursor_ResizeE: + return "right_side"; + case nzWindowCursor_ResizeN: + return "top_side"; + case nzWindowCursor_ResizeNE: + return "top_right_corner"; + case nzWindowCursor_ResizeNW: + return "top_left_corner"; + case nzWindowCursor_ResizeS: + return "bottom_side"; + case nzWindowCursor_ResizeSE: + return "bottom_right_corner"; + case nzWindowCursor_ResizeSW: + return "bottom_left_corner"; + case nzWindowCursor_ResizeW: + return "left_side"; + case nzWindowCursor_Text: + return "xterm"; + case nzWindowCursor_Wait: + return "watch"; + } + + NazaraError("Cursor is not handled by enumeration"); + return "X_cursor"; +} + +void NzWindowImpl::CommonInitialize() +{ + // Show the window + SetVisible(true); + + // Raise the window and grab input focus + SetFocus(); + + xcb_atom_t protocols[] = + { + X11::GetAtom("WM_DELETE_WINDOW"), + }; + + if (!X11::CheckCookie( + connection, + xcb_icccm_set_wm_protocols( + connection, + m_window, + X11::GetAtom("WM_PROTOCOLS"), + sizeof(protocols), + protocols + )) + ) + NazaraError("Failed to get atom for deleting a window"); + + // Flush the commands queue + xcb_flush(connection); +} + +void NzWindowImpl::ProcessEvent(xcb_generic_event_t* windowEvent) +{ + if (!m_eventListener) + return; + + // Convert the xcb event to a NzEvent + switch (windowEvent->response_type & ~0x80) + { + // Destroy event + case XCB_DESTROY_NOTIFY: + { + // The window is about to be destroyed: we must cleanup resources + CleanUp(); + break; + } + + // Gain focus event + case XCB_FOCUS_IN: + { + const uint32_t value_list[] = { eventMask }; + xcb_change_window_attributes(connection, m_window, XCB_CW_EVENT_MASK, value_list); + + NzEvent event; + event.type = nzEventType_GainedFocus; + m_parent->PushEvent(event); + + break; + } + + // Lost focus event + case XCB_FOCUS_OUT: + { + NzEvent event; + event.type = nzEventType_LostFocus; + m_parent->PushEvent(event); + + const uint32_t values[] = { XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_FOCUS_CHANGE }; + xcb_change_window_attributes(connection, m_window, XCB_CW_EVENT_MASK, values); + + break; + } + + // Resize event + case XCB_CONFIGURE_NOTIFY: + { + xcb_configure_notify_event_t* configureNotifyEvent = (xcb_configure_notify_event_t*)windowEvent; + // ConfigureNotify can be triggered for other reasons, check if the size has actually changed + if ((configureNotifyEvent->width != m_size_hints.width) || (configureNotifyEvent->height != m_size_hints.width)) + { + NzEvent event; + event.type = nzEventType_Resized; + event.size.width = configureNotifyEvent->width; + event.size.height = configureNotifyEvent->height; + m_parent->PushEvent(event); + + m_size_hints.width = configureNotifyEvent->width; + m_size_hints.height = configureNotifyEvent->height; + } + if ((configureNotifyEvent->x != m_size_hints.x) || (configureNotifyEvent->y != m_size_hints.y)) + { + NzEvent event; + event.type = nzEventType_Moved; + event.size.width = configureNotifyEvent->x; + event.size.height = configureNotifyEvent->y; + m_parent->PushEvent(event); + + m_size_hints.x = configureNotifyEvent->x; + m_size_hints.y = configureNotifyEvent->y; + } + break; + } + + // Close event + case XCB_CLIENT_MESSAGE: + { + xcb_client_message_event_t* clientMessageEvent = (xcb_client_message_event_t*)windowEvent; + + if (clientMessageEvent->type != X11::GetAtom("WM_PROTOCOLS")) + break; + if (clientMessageEvent->data.data32[0] == X11::GetAtom("WM_DELETE_WINDOW")) + { + NzEvent event; + event.type = nzEventType_Quit; + m_parent->PushEvent(event); + } + + break; + } + + // Key down event + case XCB_KEY_PRESS: + { + xcb_key_press_event_t* keyPressEvent = (xcb_key_press_event_t*)windowEvent; + + if (!m_keyRepeat && m_eventQueue.curr && m_eventQueue.next) + { + xcb_key_press_event_t* current = (xcb_key_release_event_t*)m_eventQueue.curr; + // keyPressEvent == next + + if ((current->time == keyPressEvent->time) && (current->detail == keyPressEvent->detail)) + break; + } + + auto keysym = ConvertKeyCodeToKeySym(keyPressEvent->detail, keyPressEvent->state); + + NzEvent event; + event.type = nzEventType_KeyPressed; + event.key.code = ConvertVirtualKey(keysym); + event.key.alt = keyPressEvent->state & XCB_MOD_MASK_1; + event.key.control = keyPressEvent->state & XCB_MOD_MASK_CONTROL; + event.key.shift = keyPressEvent->state & XCB_MOD_MASK_SHIFT; + event.key.system = keyPressEvent->state & XCB_MOD_MASK_4; + m_parent->PushEvent(event); + + char32_t codePoint = static_cast(keysym); + + // WTF if (std::isprint(codePoint, std::locale(""))) + handle combining ? + { + NzEvent event; + event.type = nzEventType_TextEntered; + event.text.character = codePoint; + event.text.repeated = false; + m_parent->PushEvent(event); + } + + break; + } + + // Key up event + case XCB_KEY_RELEASE: + { + xcb_key_release_event_t* keyReleaseEvent = (xcb_key_release_event_t*)windowEvent; + + if (!m_keyRepeat && m_eventQueue.curr && m_eventQueue.next) + { + // keyReleaseEvent == current + xcb_key_press_event_t* next = (xcb_key_press_event_t*)m_eventQueue.next; + + if ((keyReleaseEvent->time == next->time) && (keyReleaseEvent->detail == next->detail)) + break; + } + + auto keysym = ConvertKeyCodeToKeySym(keyReleaseEvent->detail, keyReleaseEvent->state); + + NzEvent event; + event.type = nzEventType_KeyReleased; + event.key.code = ConvertVirtualKey(keysym); + event.key.alt = keyReleaseEvent->state & XCB_MOD_MASK_1; + event.key.control = keyReleaseEvent->state & XCB_MOD_MASK_CONTROL; + event.key.shift = keyReleaseEvent->state & XCB_MOD_MASK_SHIFT; + event.key.system = keyReleaseEvent->state & XCB_MOD_MASK_4; + m_parent->PushEvent(event); + + break; + } + + // Mouse button pressed + case XCB_BUTTON_PRESS: + { + xcb_button_press_event_t* buttonPressEvent = (xcb_button_press_event_t*)windowEvent; + + NzEvent event; + event.type = nzEventType_MouseButtonPressed; + event.mouseButton.x = buttonPressEvent->event_x; + event.mouseButton.y = buttonPressEvent->event_y; + + if (buttonPressEvent->detail == XCB_BUTTON_INDEX_1) + event.mouseButton.button = NzMouse::Left; + else if (buttonPressEvent->detail == XCB_BUTTON_INDEX_2) + event.mouseButton.button = NzMouse::Middle; + else if (buttonPressEvent->detail == XCB_BUTTON_INDEX_3) + event.mouseButton.button = NzMouse::Right; + else if (buttonPressEvent->detail == XCB_BUTTON_INDEX_4) + event.mouseButton.button = NzMouse::XButton1; + else if (buttonPressEvent->detail == XCB_BUTTON_INDEX_5) + event.mouseButton.button = NzMouse::XButton2; + else + NazaraWarning("Mouse button not handled"); + + m_parent->PushEvent(event); + + break; + } + + // Mouse button released + case XCB_BUTTON_RELEASE: + { + xcb_button_release_event_t* buttonReleaseEvent = (xcb_button_release_event_t*)windowEvent; + + NzEvent event; + + switch (buttonReleaseEvent->detail) + { + case XCB_BUTTON_INDEX_4: + case XCB_BUTTON_INDEX_5: + { + event.type = nzEventType_MouseWheelMoved; + event.mouseWheel.delta = (buttonReleaseEvent->detail == XCB_BUTTON_INDEX_4) ? 1 : -1; + break; + } + default: + { + event.type = nzEventType_MouseButtonReleased; + event.mouseButton.x = buttonReleaseEvent->event_x; + event.mouseButton.y = buttonReleaseEvent->event_y; + + if (buttonReleaseEvent->detail == XCB_BUTTON_INDEX_1) + event.mouseButton.button = NzMouse::Left; + else if (buttonReleaseEvent->detail == XCB_BUTTON_INDEX_2) + event.mouseButton.button = NzMouse::Middle; + else if (buttonReleaseEvent->detail == XCB_BUTTON_INDEX_3) + event.mouseButton.button = NzMouse::Right; + else if (buttonReleaseEvent->detail == XCB_BUTTON_INDEX_4) + event.mouseButton.button = NzMouse::XButton1; + else if (buttonReleaseEvent->detail == XCB_BUTTON_INDEX_5) + event.mouseButton.button = NzMouse::XButton2; + else + NazaraWarning("Mouse button not handled"); + } + } + + m_parent->PushEvent(event); + + break; + } + + // Mouse moved + case XCB_MOTION_NOTIFY: + { + xcb_motion_notify_event_t* motionNotifyEvent = (xcb_motion_notify_event_t*)windowEvent; + + if (m_mousePos.x == motionNotifyEvent->event_x && m_mousePos.y == motionNotifyEvent->event_y) + break; + + NzEvent event; + event.type = nzEventType_MouseMoved; + event.mouseMove.deltaX = motionNotifyEvent->event_x - m_mousePos.x; + event.mouseMove.deltaY = motionNotifyEvent->event_y - m_mousePos.y; + event.mouseMove.x = motionNotifyEvent->event_x; + event.mouseMove.y = motionNotifyEvent->event_y; + m_parent->PushEvent(event); + + m_mousePos.x = motionNotifyEvent->event_x; + m_mousePos.y = motionNotifyEvent->event_y; + + break; + } + + // Mouse entered + case XCB_ENTER_NOTIFY: + { + NzEvent event; + event.type = nzEventType_MouseEntered; + m_parent->PushEvent(event); + + break; + } + + // Mouse left + case XCB_LEAVE_NOTIFY: + { + NzEvent event; + event.type = nzEventType_MouseLeft; + m_parent->PushEvent(event); + + break; + } + + // Parent window changed + case XCB_REPARENT_NOTIFY: + { + // 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_style & nzWindowStyle_Fullscreen) + SwitchToFullscreen(); + + break; + } + } +} + +void NzWindowImpl::ResetVideoMode() +{ + if (fullscreenWindow == this) + { + // Get current screen info + NzScopedXCB error(nullptr); + + // Reset the video mode + NzScopedXCB setScreenConfig(xcb_randr_set_screen_config_reply( + connection, + xcb_randr_set_screen_config( + connection, + m_oldVideoMode.root, + XCB_CURRENT_TIME, + m_oldVideoMode.config_timestamp, + m_oldVideoMode.sizeID, + m_oldVideoMode.rotation, + m_oldVideoMode.rate + ), + &error + )); + + if (error) + NazaraError("Failed to reset old screen configuration"); + + // Reset the fullscreen window + fullscreenWindow = nullptr; + } +} + +void NzWindowImpl::SetCursor(xcb_cursor_t cursor) +{ + if (!X11::CheckCookie( + connection, + xcb_change_window_attributes( + connection, + m_window, + XCB_CW_CURSOR, + &cursor + )) + ) + NazaraError("Failed to change mouse cursor"); + + xcb_flush(connection); +} + +void NzWindowImpl::SetMotifHints() +{ + NzScopedXCB error(nullptr); + + const char MOTIF_WM_HINTS[] = "_MOTIF_WM_HINTS"; + NzScopedXCB hintsAtomReply(xcb_intern_atom_reply( + connection, + xcb_intern_atom( + connection, + 0, + sizeof(MOTIF_WM_HINTS) - 1, + MOTIF_WM_HINTS + ), + &error + )); + + if (!error && hintsAtomReply) + { + const uint32_t MWM_HINTS_FUNCTIONS = 1 << 0; + const uint32_t MWM_HINTS_DECORATIONS = 1 << 1; + + //const uint32_t MWM_DECOR_ALL = 1 << 0; + const uint32_t MWM_DECOR_BORDER = 1 << 1; + const uint32_t MWM_DECOR_RESIZEH = 1 << 2; + const uint32_t MWM_DECOR_TITLE = 1 << 3; + const uint32_t MWM_DECOR_MENU = 1 << 4; + const uint32_t MWM_DECOR_MINIMIZE = 1 << 5; + const uint32_t MWM_DECOR_MAXIMIZE = 1 << 6; + + //const uint32_t MWM_FUNC_ALL = 1 << 0; + const uint32_t MWM_FUNC_RESIZE = 1 << 1; + const uint32_t MWM_FUNC_MOVE = 1 << 2; + const uint32_t MWM_FUNC_MINIMIZE = 1 << 3; + const uint32_t MWM_FUNC_MAXIMIZE = 1 << 4; + const uint32_t 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 (m_style & nzWindowStyle_Titlebar) + { + hints.decorations |= MWM_DECOR_BORDER | MWM_DECOR_TITLE | MWM_DECOR_MINIMIZE | MWM_DECOR_MENU; + hints.functions |= MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE; + } + if (m_style & nzWindowStyle_Resizable) + { + hints.decorations |= MWM_DECOR_MAXIMIZE | MWM_DECOR_RESIZEH; + hints.functions |= MWM_FUNC_MAXIMIZE | MWM_FUNC_RESIZE; + } + if (m_style & nzWindowStyle_Closable) + { + hints.decorations |= 0; + hints.functions |= MWM_FUNC_CLOSE; + } + + NzScopedXCB error(xcb_request_check( + connection, + xcb_change_property_checked( + connection, + XCB_PROP_MODE_REPLACE, + m_window, + hintsAtomReply->atom, + hintsAtomReply->atom, + 32, + 5, + &hints + ) + )); + + if (error) + NazaraError("xcb_change_property failed, could not set window hints"); + } + else + NazaraError("Failed to request _MOTIF_WM_HINTS atom."); +} + +void NzWindowImpl::SetVideoMode(const NzVideoMode& mode) +{ + // Skip mode switching if the new mode is equal to the desktop mode + if (mode == NzVideoMode::GetDesktopMode()) + return; + + NzScopedXCB error(nullptr); + + // 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) + { + // RandR extension is not supported: we cannot use fullscreen mode + NazaraError("Fullscreen is not supported, switching to window mode"); + return; + } + + // Load RandR and check its version + NzScopedXCB randrVersion(xcb_randr_query_version_reply( + connection, + xcb_randr_query_version( + connection, + 1, + 1 + ), + &error + )); + + if (error) + { + NazaraError("Failed to load RandR, switching to window mode"); + return; + } + + // Get the current configuration + NzScopedXCB config(xcb_randr_get_screen_info_reply( + connection, + xcb_randr_get_screen_info( + connection, + m_screen->root + ), + &error + )); + + if (error || !config) + { + // Failed to get the screen configuration + NazaraError("Failed to get the current screen configuration for fullscreen mode, switching to window mode"); + 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) + { + NazaraError("Failed to get the fullscreen sizes, switching to window mode"); + 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(mode.width)) && + (sizes[i].height == static_cast(mode.height))) + { + // Switch to fullscreen mode + NzScopedXCB setScreenConfig(xcb_randr_set_screen_config_reply( + connection, + xcb_randr_set_screen_config( + connection, + config->root, + XCB_CURRENT_TIME, + config->config_timestamp, + i, + config->rotation, + config->rate + ), + &error + )); + + if (error) + NazaraError("Failed to set new screen configuration"); + + // Set "this" as the current fullscreen window + fullscreenWindow = this; + return; + } + } + + NazaraError("Failed to find matching fullscreen size, switching to window mode"); +} + +void NzWindowImpl::SwitchToFullscreen() +{ + SetFocus(); + + NzScopedXCBEWMHConnection ewmhConnection(connection); + + if (!X11::CheckCookie( + connection, + xcb_ewmh_set_wm_state( + ewmhConnection, + m_window, + 1, + &ewmhConnection->_NET_WM_STATE_FULLSCREEN + )) + ) + NazaraError("Failed to switch to fullscreen"); +} + +void NzWindowImpl::UpdateEventQueue(xcb_generic_event_t* event) +{ + std::free(m_eventQueue.curr); + m_eventQueue.curr = m_eventQueue.next; + m_eventQueue.next = event; +} + +bool NzWindowImpl::UpdateNormalHints() +{ + return X11::CheckCookie( + connection, + xcb_icccm_set_wm_normal_hints( + connection, + m_window, + &m_size_hints + )); +} + +#if NAZARA_UTILITY_THREADED_WINDOW +void NzWindowImpl::WindowThread(NzWindowImpl* window, NzMutex* mutex, NzConditionVariable* condition) +{ + mutex->Lock(); + condition->Signal(); + mutex->Unlock(); // mutex et condition sont considérés invalides à partir d'ici + + if (!window->m_window) + return; + + while (window->m_threadActive) + window->ProcessEvents(true); + + window->Destroy(); +} +#endif diff --git a/src/Nazara/Utility/X11/WindowImpl.hpp b/src/Nazara/Utility/X11/WindowImpl.hpp new file mode 100644 index 000000000..8245699b2 --- /dev/null +++ b/src/Nazara/Utility/X11/WindowImpl.hpp @@ -0,0 +1,126 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +// Interface inspirée de la SFML par Laurent Gomila + +#pragma once + +#ifndef NAZARA_WINDOWIMPL_HPP +#define NAZARA_WINDOWIMPL_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#if NAZARA_UTILITY_THREADED_WINDOW +class NzConditionVariable; +class NzMutex; +#endif +class NzCursor; +class NzIcon; +class NzVideoMode; +class NzWindow; + +class NzWindowImpl : NzNonCopyable +{ + public: + NzWindowImpl(NzWindow* parent); + ~NzWindowImpl(); + + bool Create(const NzVideoMode& mode, const NzString& title, nzUInt32 style); + bool Create(NzWindowHandle handle); + + void Destroy(); + + void EnableKeyRepeat(bool enable); + void EnableSmoothScrolling(bool enable); + + NzWindowHandle GetHandle() const; + unsigned int GetHeight() const; + NzVector2i GetPosition() const; + NzVector2ui GetSize() const; + nzUInt32 GetStyle() const; + NzString GetTitle() const; + unsigned int GetWidth() const; + + bool HasFocus() const; + + void IgnoreNextMouseEvent(int mouseX, int mouseY); + + bool IsMinimized() const; + bool IsVisible() const; + + void ProcessEvents(bool block); + + void SetCursor(nzWindowCursor cursor); + void SetCursor(const NzCursor& cursor); + void SetEventListener(bool listener); + void SetFocus(); + void SetIcon(const NzIcon& icon); + void SetMaximumSize(int width, int height); + void SetMinimumSize(int width, int height); + void SetPosition(int x, int y); + void SetSize(unsigned int width, unsigned int height); + void SetStayOnTop(bool stayOnTop); + void SetTitle(const NzString& title); + void SetVisible(bool visible); + + static bool Initialize(); + static void Uninitialize(); + + private: + + void CleanUp(); + xcb_keysym_t ConvertKeyCodeToKeySym(xcb_keycode_t keycode, uint16_t state); + NzKeyboard::Key ConvertVirtualKey(xcb_keysym_t symbol); + const char* ConvertWindowCursorToXName(nzWindowCursor cursor); + void CommonInitialize(); + + void ProcessEvent(xcb_generic_event_t* windowEvent); + + void ResetVideoMode(); + + void SetCursor(xcb_cursor_t cursor); + void SetMotifHints(); + void SetVideoMode(const NzVideoMode& mode); + void SwitchToFullscreen(); + + bool UpdateNormalHints(); + void UpdateEventQueue(xcb_generic_event_t* event); + + #if NAZARA_UTILITY_THREADED_WINDOW + static void WindowThread(NzWindowImpl* window, NzMutex* mutex, NzConditionVariable* condition); + #endif + + xcb_window_t m_window; + xcb_screen_t* m_screen; + xcb_randr_get_screen_info_reply_t m_oldVideoMode; + xcb_size_hints_t m_size_hints; + nzUInt32 m_style; + #if NAZARA_UTILITY_THREADED_WINDOW + NzThread m_thread; + #endif + NzWindow* m_parent; + bool m_eventListener; + bool m_ownsWindow; + bool m_smoothScrolling; + #if NAZARA_UTILITY_THREADED_WINDOW + bool m_threadActive; + #endif + short m_scrolling; + NzVector2i m_mousePos; + bool m_keyRepeat; + + struct + { + xcb_generic_event_t* curr = nullptr; + xcb_generic_event_t* next = nullptr; + } m_eventQueue; +}; +#endif // NAZARA_WINDOWIMPL_HPP