// 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