diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67755604..f417c1e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,8 +18,8 @@ jobs: - { name: Windows VS2022 Clang, os: windows-2022, flags: -T ClangCL } - { name: Linux GCC, os: ubuntu-latest } - { name: Linux Clang, os: ubuntu-latest, flags: -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, gcovr_options: '--gcov-executable="llvm-cov-$CLANG_VERSION gcov"' } - - { name: MacOS, os: macos-latest } - - { name: MacOS Xcode, os: macos-latest, flags: -GXcode } + - { name: MacOS, os: macos-11 } + - { name: MacOS Xcode, os: macos-11, flags: -GXcode } config: - { name: Shared, flags: -DBUILD_SHARED_LIBS=TRUE } - { name: Static, flags: -DBUILD_SHARED_LIBS=FALSE } @@ -34,9 +34,9 @@ jobs: - platform: { name: Windows VS2022, os: windows-2022 } config: { name: Unity, flags: -DBUILD_SHARED_LIBS=TRUE -DCMAKE_UNITY_BUILD=ON } type: { name: Debug, flags: -DCMAKE_BUILD_TYPE=Debug -DSFML_ENABLE_COVERAGE=TRUE } - - platform: { name: MacOS, os: macos-latest } + - platform: { name: MacOS, os: macos-11 } config: { name: Frameworks, flags: -DSFML_BUILD_FRAMEWORKS=TRUE } - - platform: { name: MacOS, os: macos-latest } + - platform: { name: MacOS, os: macos-11 } config: { name: iOS, flags: -DCMAKE_TOOLCHAIN_FILE=$GITHUB_WORKSPACE/cmake/toolchains/iOS.toolchain.cmake -DIOS_PLATFORM=SIMULATOR } - platform: { name: Android, os: ubuntu-latest } config: { name: x86, flags: -DCMAKE_ANDROID_ARCH_ABI=x86 -DCMAKE_SYSTEM_NAME=Android -DSFML_BUILD_TEST_SUITE=FALSE -DCMAKE_ANDROID_NDK=$GITHUB_WORKSPACE/android-ndk-r23b -DCMAKE_ANDROID_NDK_TOOLCHAIN_VERSION=clang -DCMAKE_ANDROID_STL_TYPE=c++_shared -DCMAKE_ANDROID_API=26 } diff --git a/extlibs/headers/drm/drm-common.c b/extlibs/headers/drm/drm-common.c index 70091473..9a870054 100644 --- a/extlibs/headers/drm/drm-common.c +++ b/extlibs/headers/drm/drm-common.c @@ -170,6 +170,26 @@ static int get_resources(int fd, drmModeRes **resources) return 0; } +static int has_monitor_connected(int fd, drmModeRes* resources) +{ + int i; + drmModeConnector *connector; + for (i = 0; i < resources->count_connectors; i++) + { + connector = drmModeGetConnector(fd, resources->connectors[i]); + if (connector->connection == DRM_MODE_CONNECTED) + { + /* There is a monitor connected */ + drmModeFreeConnector(connector); + connector = NULL; + return 1; + } + drmModeFreeConnector(connector); + connector = NULL; + } + return 0; +} + #define MAX_DRM_DEVICES 64 static int find_drm_device(drmModeRes **resources) @@ -199,7 +219,11 @@ static int find_drm_device(drmModeRes **resources) if (fd < 0) continue; ret = get_resources(fd, resources); - if (!ret) + if(getenv("SFML_DRM_DEBUG")) + { + printf("DRM device used: %d\n", i); + } + if(!ret && has_monitor_connected(fd, *resources) != 0) break; close(fd); fd = -1; @@ -285,34 +309,6 @@ int init_drm(struct drm *drm, const char *device, const char *mode_str, printf("requested mode not found, using default mode!\n"); } - /* find preferred mode or the highest resolution mode: */ - if (!drm->mode) - { - for (i = 0, area = 0; i < connector->count_modes; i++) - { - drmModeModeInfo *current_mode = &connector->modes[i]; - - if (current_mode->type & DRM_MODE_TYPE_PREFERRED) - { - drm->mode = current_mode; - break; - } - - int current_area = current_mode->hdisplay * current_mode->vdisplay; - if (current_area > area) - { - drm->mode = current_mode; - area = current_area; - } - } - } - - if (!drm->mode) - { - printf("could not find mode!\n"); - return -1; - } - /* find encoder: */ for (i = 0; i < resources->count_encoders; i++) { @@ -349,7 +345,17 @@ int init_drm(struct drm *drm, const char *device, const char *mode_str, // get original display mode so we can restore display mode after program exits drm->original_crtc = drmModeGetCrtc(drm->fd, drm->crtc_id); - if(getenv("SFML_DRM_DEBUG")) + /* Let's use the current mode rather than the preferred one if the user didn't + * specify a mode with env vars + */ + if (!drm->mode) + { + if(getenv("SFML_DRM_DEBUG")) + printf("DRM using the current mode\n"); + drm->mode = &(drm->original_crtc->mode); + } + + if (getenv("SFML_DRM_DEBUG")) { printf("DRM Mode used: %s@%d\n", drm->mode->name, drm->mode->vrefresh); } diff --git a/src/SFML/Window/DRM/DRMContext.cpp b/src/SFML/Window/DRM/DRMContext.cpp index 824e7f9b..8c2e90d4 100644 --- a/src/SFML/Window/DRM/DRMContext.cpp +++ b/src/SFML/Window/DRM/DRMContext.cpp @@ -91,14 +91,18 @@ void cleanup() if (!initialized) return; - drmModeSetCrtc(drmNode.fd, - drmNode.original_crtc->crtc_id, - drmNode.original_crtc->buffer_id, - drmNode.original_crtc->x, - drmNode.original_crtc->y, - &drmNode.connector_id, - 1, - &drmNode.original_crtc->mode); + /* Avoid a modeswitch if possible */ + if (drmNode.mode != &drmNode.original_crtc->mode) + drmModeSetCrtc(drmNode.fd, + drmNode.original_crtc->crtc_id, + drmNode.original_crtc->buffer_id, + drmNode.original_crtc->x, + drmNode.original_crtc->y, + &drmNode.connector_id, + 1, + &drmNode.original_crtc->mode); + else if (getenv("SFML_DRM_DEBUG")) + printf("DRM keeping the same mode since using the original one\n"); drmModeFreeConnector(drmNode.saved_connector); drmModeFreeEncoder(drmNode.saved_encoder); diff --git a/src/SFML/Window/Unix/WindowImplX11.cpp b/src/SFML/Window/Unix/WindowImplX11.cpp index bef1f092..4427c747 100644 --- a/src/SFML/Window/Unix/WindowImplX11.cpp +++ b/src/SFML/Window/Unix/WindowImplX11.cpp @@ -46,6 +46,7 @@ #include #include +#include #include #include #include @@ -77,6 +78,7 @@ namespace WindowsImplX11Impl { sf::priv::WindowImplX11* fullscreenWindow = nullptr; std::vector allWindows; +std::bitset<256> isKeyFiltered; std::recursive_mutex allWindowsMutex; sf::String windowManagerName; @@ -88,23 +90,6 @@ constexpr unsigned long eventMask = FocusChangeMask | ButtonPressMask | ButtonRe constexpr unsigned int maxTrialsCount = 5; -// Predicate we use to find key repeat events in processEvent -struct KeyRepeatFinder -{ - KeyRepeatFinder(unsigned int initalKeycode, Time initialTime) : keycode(initalKeycode), time(initialTime) - { - } - - // Predicate operator that checks event type, keycode and timestamp - bool operator()(const XEvent& event) - { - return ((event.type == KeyPress) && (event.xkey.keycode == keycode) && (event.xkey.time - time < 2)); - } - - unsigned int keycode; - Time time; -}; - // Filter the events received by windows (only allow those matching a specific window) Bool checkEvent(::Display*, XEvent* event, XPointer userData) { @@ -831,14 +816,61 @@ void WindowImplX11::processEvents() // Pick out the events that are interesting for this window while (XCheckIfEvent(m_display, &event, &checkEvent, reinterpret_cast(m_window))) - m_events.push_back(event); - - // Handle the events for this window that we just picked out - while (!m_events.empty()) { - event = m_events.front(); - m_events.pop_front(); - processEvent(event); + // This function implements a workaround to properly discard + // repeated key events when necessary. The problem is that the + // system's key events policy doesn't match SFML's one: X server will generate + // both repeated KeyPress and KeyRelease events when maintaining a key down, while + // SFML only wants repeated KeyPress events. Thus, we have to: + // - Discard duplicated KeyRelease events when m_keyRepeat is true + // - Discard both duplicated KeyPress and KeyRelease events when m_keyRepeat is false + + bool processThisEvent = true; + + // Detect repeated key events + while (event.type == KeyRelease) + { + XEvent nextEvent; + if (XCheckIfEvent(m_display, &nextEvent, checkEvent, reinterpret_cast(m_window))) + { + if ((nextEvent.type == KeyPress) && (nextEvent.xkey.keycode == event.xkey.keycode) && + (event.xkey.time <= nextEvent.xkey.time) && (nextEvent.xkey.time <= event.xkey.time + 1)) + { + // This sequence of events comes from maintaining a key down + if (m_keyRepeat) + { + // Ignore the KeyRelease event and process the KeyPress event + event = nextEvent; + break; + } + else + { + // Ignore both events + processThisEvent = false; + break; + } + } + else + { + // This sequence of events does not come from maintaining a key down, + // so process the KeyRelease event normally, + processEvent(event); + // but loop because the next event can be the first half + // of a sequence coming from maintaining a key down. + event = nextEvent; + } + } + else + { + // No event after this KeyRelease event so assume it can be processed. + break; + } + } + + if (processThisEvent) + { + processEvent(event); + } } // Process clipboard window events @@ -1731,33 +1763,6 @@ bool WindowImplX11::processEvent(XEvent& windowEvent) { using namespace WindowsImplX11Impl; - // This function implements a workaround to properly discard - // repeated key events when necessary. The problem is that the - // system's key events policy doesn't match SFML's one: X server will generate - // both repeated KeyPress and KeyRelease events when maintaining a key down, while - // SFML only wants repeated KeyPress events. Thus, we have to: - // - Discard duplicated KeyRelease events when KeyRepeatEnabled is true - // - Discard both duplicated KeyPress and KeyRelease events when KeyRepeatEnabled is false - - // Detect repeated key events - if (windowEvent.type == KeyRelease) - { - // Find the next KeyPress event with matching keycode and time - auto it = std::find_if(m_events.begin(), - m_events.end(), - KeyRepeatFinder(windowEvent.xkey.keycode, windowEvent.xkey.time)); - - if (it != m_events.end()) - { - // If we don't want repeated events, remove the next KeyPress from the queue - if (!m_keyRepeat) - m_events.erase(it); - - // This KeyRelease is a repeated event and we don't want it - return false; - } - } - // Convert the X11 event to a sf::Event switch (windowEvent.type) { @@ -1913,10 +1918,30 @@ bool WindowImplX11::processEvent(XEvent& windowEvent) event.key.control = windowEvent.xkey.state & ControlMask; event.key.shift = windowEvent.xkey.state & ShiftMask; event.key.system = windowEvent.xkey.state & Mod4Mask; - pushEvent(event); - // Generate a TextEntered event - if (!XFilterEvent(&windowEvent, None)) + const bool filtered = XFilterEvent(&windowEvent, None); + + // Generate a KeyPressed event if needed + if (filtered) + { + pushEvent(event); + isKeyFiltered.set(windowEvent.xkey.keycode); + } + else + { + // Push a KeyPressed event if the key has never been filtered before + // (a KeyPressed event would have already been pushed if it had been filtered). + // + // Some dummy IMs (like the built-in one you get by setting XMODIFIERS=@im=none) + // never filter events away, and we have to take care of that. + // + // In addition, ignore text-only KeyPress events generated by IMs (with keycode set to 0). + if (!isKeyFiltered.test(windowEvent.xkey.keycode) && windowEvent.xkey.keycode != 0) + pushEvent(event); + } + + // Generate TextEntered events if needed + if (!filtered) { #ifdef X_HAVE_UTF8_STRING if (m_inputContext) diff --git a/src/SFML/Window/Unix/WindowImplX11.hpp b/src/SFML/Window/Unix/WindowImplX11.hpp index 1bd9c740..66b8f662 100644 --- a/src/SFML/Window/Unix/WindowImplX11.hpp +++ b/src/SFML/Window/Unix/WindowImplX11.hpp @@ -297,16 +297,15 @@ private: //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// - ::Window m_window; ///< X identifier defining our window - ::Display* m_display; ///< Pointer to the display - int m_screen; ///< Screen identifier - XIM m_inputMethod; ///< Input method linked to the X display - XIC m_inputContext; ///< Input context used to get unicode input in our window - std::deque m_events; ///< Queue we use to store pending events for this window - bool m_isExternal; ///< Tell whether the window has been created externally or by SFML - RRMode m_oldVideoMode; ///< Video mode in use before we switch to fullscreen - RRCrtc m_oldRRCrtc; ///< RRCrtc in use before we switch to fullscreen - ::Cursor m_hiddenCursor; ///< As X11 doesn't provide cursor hiding, we must create a transparent one + ::Window m_window; ///< X identifier defining our window + ::Display* m_display; ///< Pointer to the display + int m_screen; ///< Screen identifier + XIM m_inputMethod; ///< Input method linked to the X display + XIC m_inputContext; ///< Input context used to get unicode input in our window + bool m_isExternal; ///< Tell whether the window has been created externally or by SFML + RRMode m_oldVideoMode; ///< Video mode in use before we switch to fullscreen + RRCrtc m_oldRRCrtc; ///< RRCrtc in use before we switch to fullscreen + ::Cursor m_hiddenCursor; ///< As X11 doesn't provide cursor hiding, we must create a transparent one ::Cursor m_lastCursor; ///< Last cursor used -- this data is not owned by the window and is required to be always valid bool m_keyRepeat; ///< Is the KeyRepeat feature enabled? Vector2i m_previousSize; ///< Previous size of the window, to find if a ConfigureNotify event is a resize event (could be a move event only)