NazaraEngine/src/Nazara/Utility/X11/WindowImpl.cpp

1578 lines
38 KiB
C++

// 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 <Nazara/Utility/X11/WindowImpl.hpp>
#include <Nazara/Core/CallOnExit.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Utility/Cursor.hpp>
#include <Nazara/Utility/Event.hpp>
#include <Nazara/Utility/Icon.hpp>
#include <Nazara/Utility/VideoMode.hpp>
#include <Nazara/Utility/Window.hpp>
#include <Nazara/Utility/X11/CursorImpl.hpp>
#include <Nazara/Utility/X11/IconImpl.hpp>
#include <X11/keysym.h>
#include <X11/XF86keysym.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <xcb/xcb_cursor.h>
#include <xcb/xcb_keysyms.h>
#include <Nazara/Utility/Debug.hpp>
/*
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<xcb_generic_error_t> 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<xcb_generic_error_t> 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<xcb_generic_error_t> 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<xcb_generic_error_t> error(nullptr);
NzScopedXCB<xcb_get_input_focus_reply_t> 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<xcb_generic_error_t> 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<xcb_generic_error_t> 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<uint32_t>(x), static_cast<uint32_t>(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<char32_t>(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<xcb_generic_error_t> error(nullptr);
// Reset the video mode
NzScopedXCB<xcb_randr_set_screen_config_reply_t> 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<xcb_generic_error_t> error(nullptr);
const char MOTIF_WM_HINTS[] = "_MOTIF_WM_HINTS";
NzScopedXCB<xcb_intern_atom_reply_t> 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<xcb_generic_error_t> 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<xcb_generic_error_t> 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<xcb_randr_query_version_reply_t> 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<xcb_randr_get_screen_info_reply_t> 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<int>(mode.width)) &&
(sizes[i].height == static_cast<int>(mode.height)))
{
// Switch to fullscreen mode
NzScopedXCB<xcb_randr_set_screen_config_reply_t> 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