SDL: Up-to-date QNX support (#14806)

From f4a541682a3ac70302636db235995b39741a85e3 Mon Sep 17 00:00:00 2001
From: eleir9268 <[EMAIL REDACTED]>
Date: Fri, 30 Jan 2026 18:32:43 -0500
Subject: [PATCH] Up-to-date QNX support (#14806)

Co-authored-by: Roberto Speranza <rsperanza@qnx.com>
Co-authored-by: Darcy Phipps <dphipps@qnx.com>
Co-authored-by: Pierce McKinnon <pimckinnon@qnx.com>
---
 CMakeLists.txt                      |   2 +-
 docs/README-qnx.md                  |  44 ++++
 include/SDL3/SDL_platform_defines.h |   2 +-
 include/SDL3/SDL_video.h            |   9 +
 src/thread/pthread/SDL_systhread.c  |   5 +
 src/video/SDL_egl.c                 |   7 +
 src/video/SDL_video.c               |   2 +-
 src/video/qnx/SDL_qnx.h             |  49 +++-
 src/video/qnx/SDL_qnxgl.c           | 203 ++++++++++++---
 src/video/qnx/SDL_qnxkeyboard.c     |  92 ++++++-
 src/video/qnx/SDL_qnxmodes.c        | 195 +++++++++++++++
 src/video/qnx/SDL_qnxmouse.c        | 189 ++++++++++++++
 src/video/qnx/SDL_qnxpointer.c      |  89 +++++++
 src/video/qnx/SDL_qnxvideo.c        | 374 +++++++++++++++++++++++++---
 14 files changed, 1177 insertions(+), 85 deletions(-)
 create mode 100644 docs/README-qnx.md
 create mode 100644 src/video/qnx/SDL_qnxmodes.c
 create mode 100644 src/video/qnx/SDL_qnxmouse.c
 create mode 100644 src/video/qnx/SDL_qnxpointer.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1211d81acc63c..2221a3b29059d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1813,7 +1813,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU)
           "${SDL3_SOURCE_DIR}/src/audio/netbsd/*.h"
         )
         set(HAVE_SDL_AUDIO TRUE)
-    elseif(QNX)
+    elseif(QNX AND (CMAKE_SYSTEM_VERSION VERSION_LESS "8.0.0"))
         set(SDL_AUDIO_DRIVER_QNX 1)
         sdl_glob_sources(
           "${SDL3_SOURCE_DIR}/src/audio/qnx/*.c"
diff --git a/docs/README-qnx.md b/docs/README-qnx.md
new file mode 100644
index 0000000000000..bf08e2a78462e
--- /dev/null
+++ b/docs/README-qnx.md
@@ -0,0 +1,44 @@
+QNX
+=======
+
+SDL port for QNX, providing both screen and Wayland video backends.
+
+This was originally contributed by Elad Lahav for QNX 7.0.
+
+The port was later improved and adapted for QNX 8.0 by:
+- Ethan Leir
+- Roberto Speranza
+- Darcy Phipps
+- Jai Moraes
+- Pierce McKinnon
+
+Further changes to enable Wayland with the EGL backend were made by Felix Xing
+and Aaron Bassett.
+
+
+## Building
+
+Building SDL3 for QNX requires Wayland to be built and installed. The commands
+to build it are,
+```bash
+# Note, if you're cross-compiling, you will need to source qnxsdp-env.sh and
+# provide the path to a cmake toolchain file with -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN_DIR/qnx.nto.toolchain.cmake
+cmake -B build -DCMAKE_BUILD_TYPE=Release -DSDL_X11=0
+cmake --build build
+cmake --install build
+```
+
+## QNX self-hosted
+
+QNX provides a self-hosted environment, available with [the free license](https://www.qnx.com/products/everywhere/).
+This is the easiest way to get your hands on SDL.
+
+## QNX build-files
+
+You can find the cross-compiled build tools at https://github.com/qnx-ports/build-files
+
+## Notes - screen
+
+- Currently, only software and OpenGLES2 rendering is supported.
+- Unless your application is managed by a window manager capable of closing the application, you will need to quit it yourself.
+- Restraining the mouse to a window or warping the mouse cursor will not work.
diff --git a/include/SDL3/SDL_platform_defines.h b/include/SDL3/SDL_platform_defines.h
index 79631491de2f3..526bc0eaa1767 100644
--- a/include/SDL3/SDL_platform_defines.h
+++ b/include/SDL3/SDL_platform_defines.h
@@ -277,7 +277,7 @@
 #define SDL_PLATFORM_OSF 1
 #endif
 
-#ifdef __QNXNTO__
+#if defined(__QNXNTO__) || defined(__QNX__)
 
 /**
  * A preprocessor macro that is only defined if compiling for QNX Neutrino.
diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h
index 2f062959fe6b7..da6413ed55bc9 100644
--- a/include/SDL3/SDL_video.h
+++ b/include/SDL3/SDL_video.h
@@ -1560,6 +1560,13 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowParent(SDL_Window *window)
  * - `SDL_PROP_WINDOW_OPENVR_OVERLAY_ID_NUMBER`: the OpenVR Overlay Handle ID
  *   for the associated overlay window.
  *
+ * On QNX:
+ *
+ * - `SDL_PROP_WINDOW_QNX_WINDOW_POINTER`: the screen_window_t associated with
+ *   the window.
+ * - `SDL_PROP_WINDOW_QNX_SURFACE_POINTER`: the EGLSurface associated with
+ *   the window
+ *
  * On Vivante:
  *
  * - `SDL_PROP_WINDOW_VIVANTE_DISPLAY_POINTER`: the EGLNativeDisplayType
@@ -1644,6 +1651,8 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window
 #define SDL_PROP_WINDOW_COCOA_WINDOW_POINTER                        "SDL.window.cocoa.window"
 #define SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER                 "SDL.window.cocoa.metal_view_tag"
 #define SDL_PROP_WINDOW_OPENVR_OVERLAY_ID_NUMBER                    "SDL.window.openvr.overlay_id"
+#define SDL_PROP_WINDOW_QNX_WINDOW_POINTER                          "SDL.window.qnx.window"
+#define SDL_PROP_WINDOW_QNX_SURFACE_POINTER                         "SDL.window.qnx.surface"
 #define SDL_PROP_WINDOW_VIVANTE_DISPLAY_POINTER                     "SDL.window.vivante.display"
 #define SDL_PROP_WINDOW_VIVANTE_WINDOW_POINTER                      "SDL.window.vivante.window"
 #define SDL_PROP_WINDOW_VIVANTE_SURFACE_POINTER                     "SDL.window.vivante.surface"
diff --git a/src/thread/pthread/SDL_systhread.c b/src/thread/pthread/SDL_systhread.c
index 5ad354f8f2cc6..37001982c7907 100644
--- a/src/thread/pthread/SDL_systhread.c
+++ b/src/thread/pthread/SDL_systhread.c
@@ -252,7 +252,12 @@ bool SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority)
     if (priority == SDL_THREAD_PRIORITY_LOW) {
         sched.sched_priority = sched_get_priority_min(policy);
     } else if (priority == SDL_THREAD_PRIORITY_TIME_CRITICAL) {
+#if defined(__QNX__)
+        /* io_snd complains about a client thread having priority >= 49 */
+        sched.sched_priority = 48;
+#else
         sched.sched_priority = sched_get_priority_max(policy);
+#endif
     } else {
         int min_priority = sched_get_priority_min(policy);
         int max_priority = sched_get_priority_max(policy);
diff --git a/src/video/SDL_egl.c b/src/video/SDL_egl.c
index 1d35ef0dd47ad..6a45f1c2bec7c 100644
--- a/src/video/SDL_egl.c
+++ b/src/video/SDL_egl.c
@@ -102,6 +102,13 @@
 #define DEFAULT_OGL_ES_PVR "libGLES_CM.so"
 #define DEFAULT_OGL_ES     "libGLESv1_CM.so"
 
+#elif defined(SDL_VIDEO_DRIVER_QNX)
+// QNX
+#define DEFAULT_EGL        "libEGL.so.1"
+#define DEFAULT_OGL_ES2    "libGLESv2.so.1"
+#define DEFAULT_OGL_ES_PVR "libGLESv2.so.1"
+#define DEFAULT_OGL_ES     "libGLESv2.so.1"
+
 #else
 // Desktop Linux/Unix-like
 #define DEFAULT_OGL        "libGL.so.1"
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index 287e0b482ca47..2c63af15be8f7 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -279,7 +279,7 @@ typedef struct
 
 static Uint32 SDL_DefaultGraphicsBackends(SDL_VideoDevice *_this)
 {
-#if (defined(SDL_VIDEO_OPENGL) && defined(SDL_PLATFORM_MACOS)) || (defined(SDL_PLATFORM_IOS) && !TARGET_OS_MACCATALYST)
+#if (defined(SDL_VIDEO_OPENGL) && defined(SDL_PLATFORM_MACOS)) || (defined(SDL_PLATFORM_IOS) && !TARGET_OS_MACCATALYST) || defined(SDL_PLATFORM_QNXNTO)
     if (_this->GL_CreateContext) {
         return SDL_WINDOW_OPENGL;
     }
diff --git a/src/video/qnx/SDL_qnx.h b/src/video/qnx/SDL_qnx.h
index f8a50cfa7359e..b426d3a0f1bfe 100644
--- a/src/video/qnx/SDL_qnx.h
+++ b/src/video/qnx/SDL_qnx.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 2017 BlackBerry Limited
+  Copyright (C) 2026 BlackBerry Limited
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -26,23 +26,62 @@
 #include <screen/screen.h>
 #include <EGL/egl.h>
 
-typedef struct
+typedef struct SDL_DisplayData
+{
+    screen_display_t screen_display;
+} SDL_DisplayData;
+
+typedef struct SDL_DisplayModeData
+{
+    int                   screen_format;
+    screen_display_mode_t screen_display_mode;
+} SDL_DisplayModeData;
+
+typedef struct SDL_WindowData
 {
     screen_window_t window;
     EGLSurface      surface;
     EGLConfig       conf;
-} window_impl_t;
+    SDL_GLContext   context;
+    int             resize;
+    bool            has_focus;
+} SDL_WindowData;
+
+typedef struct SDL_CursorData
+{
+    screen_session_t session;
+    int              realized_shape;
+    bool             is_visible;
+} SDL_CursorData;
+
+typedef struct SDL_MouseData
+{
+    int      x_prev;
+    int      y_prev;
+} SDL_MouseData;
+
+extern screen_context_t * getContext();
+extern screen_event_t * getEvent();
 
 extern void handleKeyboardEvent(screen_event_t event);
+extern void handlePointerEvent(screen_event_t event);
 
-extern bool glGetConfig(EGLConfig *pconf, int *pformat);
+extern bool glInitConfig(SDL_WindowData *impl, int *pformat);
 extern bool glLoadLibrary(SDL_VideoDevice *_this, const char *name);
 extern SDL_FunctionPointer glGetProcAddress(SDL_VideoDevice *_this, const char *proc);
 extern SDL_GLContext glCreateContext(SDL_VideoDevice *_this, SDL_Window *window);
 extern bool glSetSwapInterval(SDL_VideoDevice *_this, int interval);
 extern bool glSwapWindow(SDL_VideoDevice *_this, SDL_Window *window);
 extern bool glMakeCurrent(SDL_VideoDevice *_this, SDL_Window * window, SDL_GLContext context);
-extern void glDeleteContext(SDL_VideoDevice *_this, SDL_GLContext context);
+extern bool glDeleteContext(SDL_VideoDevice *_this, SDL_GLContext context);
 extern void glUnloadLibrary(SDL_VideoDevice *_this);
 
+extern SDL_PixelFormat screenToPixelFormat(int screen_format);
+extern bool getDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display);
+extern bool setDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode);
+extern bool getDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect);
+
+extern void initMouse(SDL_VideoDevice *_this);
+extern void quitMouse(SDL_VideoDevice *_this);
+
 #endif
diff --git a/src/video/qnx/SDL_qnxgl.c b/src/video/qnx/SDL_qnxgl.c
index 639e556900e27..4fe07aa660500 100644
--- a/src/video/qnx/SDL_qnxgl.c
+++ b/src/video/qnx/SDL_qnxgl.c
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 2017 BlackBerry Limited
+  Copyright (C) 2026 BlackBerry Limited
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -19,11 +19,119 @@
   3. This notice may not be removed or altered from any source distribution.
 */
 
-#include "../../SDL_internal.h"
+#include "SDL_internal.h"
 #include "SDL_qnx.h"
 
 static EGLDisplay   egl_disp;
 
+struct DummyConfig
+{
+    int red_size;
+    int green_size;
+    int blue_size;
+    int alpha_size;
+    int native_id;
+};
+
+static struct DummyConfig getDummyConfigFromScreenSettings(int format)
+{
+    struct DummyConfig dummyConfig= {};
+
+    dummyConfig.native_id = format;
+    switch (format) {
+         case SCREEN_FORMAT_RGBX4444:
+            dummyConfig.red_size = 4;
+            dummyConfig.green_size = 4;
+            dummyConfig.blue_size = 4;
+            dummyConfig.alpha_size = 4;
+            break;
+         case SCREEN_FORMAT_RGBA5551:
+            dummyConfig.red_size = 5;
+            dummyConfig.green_size = 5;
+            dummyConfig.blue_size = 5;
+            dummyConfig.alpha_size = 1;
+            break;
+         case SCREEN_FORMAT_RGB565:
+            dummyConfig.red_size = 5;
+            dummyConfig.green_size = 6;
+            dummyConfig.blue_size = 5;
+            dummyConfig.alpha_size = 0;
+            break;
+         case SCREEN_FORMAT_RGB888:
+            dummyConfig.red_size = 8;
+            dummyConfig.green_size = 8;
+            dummyConfig.blue_size = 8;
+            dummyConfig.alpha_size = 0;
+            break;
+            case SCREEN_FORMAT_BGRA8888:
+            case SCREEN_FORMAT_BGRX8888:
+            case SCREEN_FORMAT_RGBA8888:
+         case SCREEN_FORMAT_RGBX8888:
+            dummyConfig.red_size = 8;
+            dummyConfig.green_size = 8;
+            dummyConfig.blue_size = 8;
+            dummyConfig.alpha_size = 8;
+            break;
+            default:
+                break;
+    }
+    return dummyConfig;
+}
+
+static EGLConfig chooseConfig(struct DummyConfig dummyConfig, EGLConfig* egl_configs, EGLint egl_num_configs)
+{
+   EGLConfig glConfig = (EGLConfig)0;
+
+    for (size_t ii = 0; ii < egl_num_configs; ii++) {
+        EGLint val;
+
+        eglGetConfigAttrib(egl_disp, egl_configs[ii], EGL_SURFACE_TYPE, &val);
+        if (!(val & EGL_WINDOW_BIT)) {
+            continue;
+        }
+
+        eglGetConfigAttrib(egl_disp, egl_configs[ii], EGL_RENDERABLE_TYPE, &val);
+        if (!(val & EGL_OPENGL_ES2_BIT)) {
+            continue;
+        }
+
+        eglGetConfigAttrib(egl_disp, egl_configs[ii], EGL_DEPTH_SIZE, &val);
+        if (val == 0) {
+            continue;
+        }
+
+        eglGetConfigAttrib(egl_disp, egl_configs[ii], EGL_RED_SIZE, &val);
+        if (val != dummyConfig.red_size) {
+           continue;
+        }
+
+        eglGetConfigAttrib(egl_disp, egl_configs[ii], EGL_GREEN_SIZE, &val);
+        if (val != dummyConfig.green_size) {
+           continue;
+        }
+
+        eglGetConfigAttrib(egl_disp, egl_configs[ii], EGL_BLUE_SIZE, &val);
+        if (val != dummyConfig.blue_size) {
+           continue;
+        }
+
+        eglGetConfigAttrib(egl_disp, egl_configs[ii], EGL_ALPHA_SIZE, &val);
+        if (val != dummyConfig.alpha_size) {
+            continue;
+        }
+        if(!glConfig)
+        {
+            glConfig = egl_configs[ii];
+        }
+
+        eglGetConfigAttrib(egl_disp, egl_configs[ii], EGL_NATIVE_VISUAL_ID, &val);
+        if ((val != 0) && (val == dummyConfig.native_id)) {
+            return egl_configs[ii];
+        }
+    }
+    return glConfig;
+}
+
 /**
  * Detertmines the pixel format to use based on the current display and EGL
  * configuration.
@@ -42,7 +150,7 @@ static int chooseFormat(EGLConfig egl_conf)
         case 32:
             return SCREEN_FORMAT_RGBX8888;
         case 24:
-            return SDL_PIXELFORMAT_RGB24;
+            return SCREEN_FORMAT_RGB888;
         case 16:
             switch (alpha_bit_depth) {
                 case 4:
@@ -59,20 +167,18 @@ static int chooseFormat(EGLConfig egl_conf)
 
 /**
  * Enumerates the supported EGL configurations and chooses a suitable one.
- * @param[out]  pconf   The chosen configuration
  * @param[out]  pformat The chosen pixel format
- * @return true if successful, -1 on error
+ * @return true if successful, false on error
  */
-bool glGetConfig(EGLConfig *pconf, int *pformat)
+bool glInitConfig(SDL_WindowData *impl, int *pformat)
 {
     EGLConfig egl_conf = (EGLConfig)0;
     EGLConfig *egl_configs;
     EGLint egl_num_configs;
-    EGLint val;
     EGLBoolean rc;
-    EGLint i;
+    struct DummyConfig dummyconfig = {};
 
-    // Determine the numbfer of configurations.
+    // Determine the number of configurations.
     rc = eglGetConfigs(egl_disp, NULL, 0, &egl_num_configs);
     if (rc != EGL_TRUE) {
         return false;
@@ -96,30 +202,12 @@ bool glGetConfig(EGLConfig *pconf, int *pformat)
         return false;
     }
 
-    // Find a good configuration.
-    for (i = 0; i < egl_num_configs; i++) {
-        eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_SURFACE_TYPE, &val);
-        if (!(val & EGL_WINDOW_BIT)) {
-            continue;
-        }
-
-        eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_RENDERABLE_TYPE, &val);
-        if (!(val & EGL_OPENGL_ES2_BIT)) {
-            continue;
-        }
-
-        eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_DEPTH_SIZE, &val);
-        if (val == 0) {
-            continue;
-        }
-
-        egl_conf = egl_configs[i];
-        break;
-    }
+    dummyconfig = getDummyConfigFromScreenSettings(*pformat);
+    egl_conf = chooseConfig(dummyconfig, egl_configs, egl_num_configs);
+    *pformat = chooseFormat(egl_conf);
 
     SDL_free(egl_configs);
-    *pconf = egl_conf;
-    *pformat = chooseFormat(egl_conf);
+    impl->conf = egl_conf;
 
     return true;
 }
@@ -128,7 +216,7 @@ bool glGetConfig(EGLConfig *pconf, int *pformat)
  * Initializes the EGL library.
  * @param   SDL_VideoDevice *_this
  * @param   name    unused
- * @return  0 if successful, -1 on error
+ * @return  true if successful, false on error
  */
 bool glLoadLibrary(SDL_VideoDevice *_this, const char *name)
 {
@@ -165,7 +253,7 @@ SDL_FunctionPointer glGetProcAddress(SDL_VideoDevice *_this, const char *proc)
  */
 SDL_GLContext glCreateContext(SDL_VideoDevice *_this, SDL_Window *window)
 {
-    window_impl_t   *impl = (window_impl_t *)window->internal;
+    SDL_WindowData   *impl = (SDL_WindowData *)window->internal;
     EGLContext      context;
     EGLSurface      surface;
 
@@ -201,6 +289,10 @@ SDL_GLContext glCreateContext(SDL_VideoDevice *_this, SDL_Window *window)
     eglMakeCurrent(egl_disp, surface, surface, context);
 
     impl->surface = surface;
+    impl->context = context;
+
+    SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_QNX_SURFACE_POINTER, impl->surface);
+
     return context;
 }
 
@@ -208,7 +300,7 @@ SDL_GLContext glCreateContext(SDL_VideoDevice *_this, SDL_Window *window)
  * Sets a new value for the number of frames to display before swapping buffers.
  * @param   SDL_VideoDevice *_this
  * @param   interval    New interval value
- * @return  0 if successful, -1 on error
+ * @return  true if successful, false on error
  */
 bool glSetSwapInterval(SDL_VideoDevice *_this, int interval)
 {
@@ -223,13 +315,44 @@ bool glSetSwapInterval(SDL_VideoDevice *_this, int interval)
  * Swaps the EGL buffers associated with the given window
  * @param   SDL_VideoDevice *_this
  * @param   window  Window to swap buffers for
- * @return  0 if successful, -1 on error
+ * @return  true if successful, false on error
  */
 bool glSwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
 {
     // !!! FIXME: should we migrate this all over to use SDL_egl.c?
-    window_impl_t   *impl = (window_impl_t *)window->internal;
-    return eglSwapBuffers(egl_disp, impl->surface) == EGL_TRUE ? 0 : -1;
+    SDL_WindowData   *impl = (SDL_WindowData *)window->internal;
+    {
+        if (impl->resize) {
+            EGLSurface surface;
+            struct {
+                EGLint render_buffer[2];
+                EGLint none;
+            } egl_surf_attr = {
+                .render_buffer = { EGL_RENDER_BUFFER, EGL_BACK_BUFFER },
+                .none = EGL_NONE
+            };
+
+            if (eglMakeCurrent(egl_disp, NULL, NULL, impl->context) != EGL_TRUE) {
+                return false;
+            }
+            eglDestroySurface(egl_disp, impl->surface);
+
+            surface = eglCreateWindowSurface(egl_disp, impl->conf, impl->window,
+                                     (EGLint *)&egl_surf_attr);
+            if (surface == EGL_NO_SURFACE) {
+                return false;
+            }
+
+            if (eglMakeCurrent(egl_disp, surface, surface, impl->context) != EGL_TRUE) {
+                return false;
+            }
+
+            impl->surface = surface;
+            impl->resize = 0;
+        }
+    }
+
+    return eglSwapBuffers(egl_disp, impl->surface) == EGL_TRUE ? true : false;
 }
 
 /**
@@ -237,15 +360,15 @@ bool glSwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
  * @param   SDL_VideoDevice *_this
  * @param   window  SDL window associated with the context (maybe NULL)
  * @param   context The context to activate
- * @return  0 if successful, -1 on error
+ * @return  true if successful, false on error
  */
 bool glMakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context)
 {
-    window_impl_t   *impl;
+    SDL_WindowData   *impl;
     EGLSurface      surface = NULL;
 
     if (window) {
-        impl = (window_impl_t *)window->internal;
+        impl = (SDL_WindowData *)window->internal;
         surface = impl->surface;
     }
 
diff --git a/src/video/qnx/SDL_qnxkeyboard.c b/src/video/qnx/SDL_qnxkeyboard.c
index b224caa25bc06..44f953b45fc85 100644
--- a/src/video/qnx/SDL_qnxkeyboard.c
+++ b/src/video/qnx/SDL_qnxkeyboard.c
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 2017 BlackBerry Limited
+  Copyright (C) 2026 BlackBerry Limited
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -19,7 +19,7 @@
   3. This notice may not be removed or altered from any source distribution.
 */
 
-#include "../../SDL_internal.h"
+#include "SDL_internal.h"
 #include "../../events/SDL_keyboard_c.h"
 #include "SDL3/SDL_scancode.h"
 #include "SDL3/SDL_events.h"
@@ -84,10 +84,81 @@ static int key_to_sdl[] = {
     [KEYCODE_LEFT] = SDL_SCANCODE_LEFT,
     [KEYCODE_PG_UP] = SDL_SCANCODE_PAGEUP,
     [KEYCODE_PG_DOWN] = SDL_SCANCODE_PAGEDOWN,
+    [KEYCODE_PRINT] = SDL_SCANCODE_PRINTSCREEN,
+    [KEYCODE_SCROLL_LOCK] = SDL_SCANCODE_SCROLLLOCK,
+    [KEYCODE_PAUSE] = SDL_SCANCODE_PAUSE,
+    [KEYCODE_INSERT] = SDL_SCANCODE_INSERT,
+    [KEYCODE_HOME] = SDL_SCANCODE_HOME,
+    [KEYCODE_DELETE] = SDL_SCANCODE_DELETE,
+    [KEYCODE_END] = SDL_SCANCODE_END,
+    [KEYCODE_NUM_LOCK] = SDL_SCANCODE_NUMLOCKCLEAR,
     [KEYCODE_RIGHT] = SDL_SCANCODE_RIGHT,
     [KEYCODE_RETURN] = SDL_SCANCODE_RETURN,
     [KEYCODE_TAB] = SDL_SCANCODE_TAB,
     [KEYCODE_ESCAPE] = SDL_SCANCODE_ESCAPE,
+    [KEYCODE_LEFT_CTRL] = SDL_SCANCODE_LCTRL,
+    [KEYCODE_RIGHT_CTRL] = SDL_SCANCODE_RCTRL,
+    [KEYCODE_LEFT_SHIFT] = SDL_SCANCODE_LSHIFT,
+    [KEYCODE_RIGHT_SHIFT] = SDL_SCANCODE_RSHIFT,
+    [KEYCODE_LEFT_ALT] = SDL_SCANCODE_LALT,
+    [KEYCODE_RIGHT_ALT] = SDL_SCANCODE_RALT,
+    [KEYCODE_BACKSPACE] = SDL_SCANCODE_BACKSPACE,
+    [KEYCODE_CAPS_LOCK] = SDL_SCANCODE_CAPSLOCK,
+    [KEYCODE_F1] = SDL_SCANCODE_F1,
+    [KEYCODE_F2] = SDL_SCANCODE_F2,
+    [KEYCODE_F3] = SDL_SCANCODE_F3,
+    [KEYCODE_F4] = SDL_SCANCODE_F4,
+    [KEYCODE_F5] = SDL_SCANCODE_F5,
+    [KEYCODE_F6] = SDL_SCANCODE_F6,
+    [KEYCODE_F7] = SDL_SCANCODE_F7,
+    [KEYCODE_F8] = SDL_SCANCODE_F8,
+    [KEYCODE_F9] = SDL_SCANCODE_F9,
+    [KEYCODE_F10] = SDL_SCANCODE_F10,
+    [KEYCODE_F11] = SDL_SCANCODE_F11,
+    [KEYCODE_F12] = SDL_SCANCODE_F12,
+    [KEYCODE_KP_DIVIDE] = SDL_SCANCODE_KP_DIVIDE,
+    [KEYCODE_KP_MULTIPLY] = SDL_SCANCODE_KP_MULTIPLY,
+    [KEYCODE_KP_MINUS] = SDL_SCANCODE_KP_MINUS,
+    [KEYCODE_KP_PLUS] = SDL_SCANCODE_KP_PLUS,
+    [KEYCODE_KP_ENTER] = SDL_SCANCODE_KP_ENTER,
+    /* NO SCREEN MAPPING FOR KEYPAD DIGITS
+    [KEYCODE_ZERO] = SDL_SCANCODE_KP_0,
+    [KEYCODE_ONE] = SDL_SCANCODE_KP_1,
+    [KEYCODE_TWO] = SDL_SCANCODE_KP_2,
+    [KEYCODE_THREE] = SDL_SCANCODE_KP_3,
+    [KEYCODE_FOUR] = SDL_SCANCODE_KP_4,
+    [KEYCODE_FIVE] = SDL_SCANCODE_KP_5,
+    [KEYCODE_SIX] = SDL_SCANCODE_KP_6,
+    [KEYCODE_SEVEN] = SDL_SCANCODE_KP_7,
+    [KEYCODE_EIGHT] = SDL_SCANCODE_KP_8,
+    [KEYCODE_NINE] = SDL_SCANCODE_KP_9,
+    [KEYCODE_PERIOD] = SDL_SCANCODE_KP_PERIOD,
+    */
+    [KEYCODE_POWER] = SDL_SCANCODE_POWER,
+    [KEYCODE_PLAY] = SDL_SCANCODE_EXECUTE,
+    [KEYCODE_HELP] = SDL_SCANCODE_HELP,
+    [KEYCODE_MENU] = SDL_SCANCODE_MENU,
+    [KEYCODE_AC_SELECT_ALL] = SDL_SCANCODE_SELECT,
+    [KEYCODE_STOP] = SDL_SCANCODE_STOP,
+    [KEYCODE_AC_UNDO] = SDL_SCANCODE_UNDO,
+    [KEYCODE_AC_CUT] = SDL_SCANCODE_CUT,
+    [KEYCODE_AC_COPY] = SDL_SCANCODE_COPY,
+    [KEYCODE_AC_PASTE] = SDL_SCANCODE_PASTE,
+    [KEYCODE_AC_FIND] = SDL_SCANCODE_FIND,
+    [KEYCODE_MUTE] = SDL_SCANCODE_MUTE,
+    [KEYCODE_VOLUME_UP] = SDL_SCANCODE_VOLUMEUP,
+    [KEYCODE_VOLUME_DOWN] = SDL_SCANCODE_VOLUMEDOWN,
+    [KEYCODE_SYSREQ] = SDL_SCANCODE_SYSREQ,
+    [KEYCODE_AC_CANCEL] = SDL_SCANCODE_CANCEL,
+    [KEYCODE_AC_SEARCH] = SDL_SCANCODE_AC_SEARCH,
+    [KEYCODE_AC_HOME] = SDL_SCANCODE_AC_HOME,
+    [KEYCODE_AC_BACK] = SDL_SCANCODE_AC_BACK,
+    [KEYCODE_AC_FORWARD] = SDL_SCANCODE_AC_FORWARD,
+    [KEYCODE_AC_STOP] = SDL_SCANCODE_AC_STOP,
+    [KEYCODE_AC_REFRESH] = SDL_SCANCODE_AC_REFRESH,
+    [KEYCODE_AC_BOOKMARKS] = SDL_SCANCODE_AC_BOOKMARKS,
+    [KEYCODE_EJECT] = SDL_SCANCODE_MEDIA_EJECT,
+    [KEYCODE_SLEEP] = SDL_SCANCODE_SLEEP,
 };
 
 /**
@@ -98,6 +169,8 @@ static int key_to_sdl[] = {
 void handleKeyboardEvent(screen_event_t event)
 {
     int             val;
+    int             cap;
+    char            ascii_text[2];
     SDL_Scancode    scancode;
 
     // Get the key value.
@@ -105,6 +178,10 @@ void handleKeyboardEvent(screen_event_t event)
         return;
     }
 
+    if (screen_get_event_property_iv(event, SCREEN_PROPERTY_KEY_CAP, &cap) < 0) {
+        return;
+    }
+
     // Skip unrecognized keys.
     if ((val < 0) || (val >= SDL_arraysize(key_to_sdl))) {
         return;
@@ -126,6 +203,17 @@ void handleKeyboardEvent(screen_event_t event)
     // Need to handle more key states (such as key combinations).
     if (val & KEY_DOWN) {
         SDL_SendKeyboardKey(0, SDL_DEFAULT_KEYBOARD_ID, val, scancode, true);
+
+        // TODO: To simplify, we're ignoring keycodes that aren't just ascii.
+        if ((val < UNICODE_PRIVATE_USE_AREA_FIRST) && ((cap & 0xFF) == cap)) {
+            ascii_text[0] = cap;
+            ascii_text[1] = 0;
+            SDL_SendKeyboardText(ascii_text);
+        } else if ((KEYCODE_PC_KEYS <= val) && (val < KEYCODE_CONSUMER_KEYS)) {
+            ascii_text[0] = val & 0xFF;
+            ascii_text[1] = 0;
+            SDL_SendKeyboardText(ascii_text);
+        }
     } else {
         SDL_SendKeyboardKey(0, SDL_DEFAULT_KEYBOARD_ID, val, scancode, false);
     }
diff --git a/src/video/qnx/SDL_qnxmodes.c b/src/video/qnx/SDL_qnxmodes.c
new file mode 100644
index 0000000000000..cd7702aa78754
--- /dev/null
+++ b/src/video/qnx/SDL_qnxmodes.c
@@ -0,0 +1,195 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 2026 BlackBerry Limited
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include "SDL_internal.h"
+#include "../SDL_sysvideo.h"
+#include "../../events/SDL_keyboard_c.h"
+#include "../../events/SDL_mouse_c.h"
+#include "../../events/SDL_windowevents_c.h"
+#include "SDL_qnx.h"
+
+#include <errno.h>
+
+// All indices not already assigned will be zero'd to SDL_PIXELFORMAT_UNKNOWN.
+static const SDL_PixelFormat _format_map[] = {
+    [SCREEN_FORMAT_RGBA4444] = SDL_PIXELFORMAT_RGBA4444,
+    [SCREEN_FORMAT_RGBA5551] = SDL_PIXELFORMAT_RGBA5551,
+    [SCREEN_FORMAT_RGB565] = SDL_PIXELFORMAT_RGB565,
+    [SCREEN_FORMAT_RGBA8888] = SDL_PIXELFORMAT_RGBA8888,
+    [SCREEN_FORMAT_RGBX8888] = SDL_PIXELFORMAT_RGBX8888,
+    [SCREEN_FORMAT_NV12] = SDL_PIXELFORMAT_NV12,
+    [SCREEN_FORMAT_YV12] = SDL_PIXELFORMAT_YV12,
+    [SCREEN_FORMAT_UYVY] = SDL_PIXELFORMAT_UYVY,
+    [SCREEN_FORMAT_YUY2] = SDL_PIXELFORMAT_YUY2,
+    [SCREEN_FORMAT_YVYU] = SDL_PIXELFORMAT_YVYU,
+    [SCREEN_FORMAT_P010] = SDL_PIXELFORMAT_P010,
+    [SCREEN_FORMAT_BGRA8888] = SDL_PIXELFORMAT_BGRA8888,
+    [SCREEN_FORMAT_BGRX8888] = SDL_PIXELFORMAT_BGRX8888,
+};
+
+SDL_PixelFormat screenToPixelFormat(int screen_format)
+{
+    if ((screen_format < 0) || (screen_format >= SDL_arraysize(_format_map))) {
+        return SDL_PIXELFORMAT_UNKNOWN;
+    }
+
+    return _format_map[screen_format];
+}
+
+bool getDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
+{
+    SDL_DisplayData     *display_data = display->internal;
+    SDL_DisplayMode     display_mode;
+    SDL_DisplayModeData *display_mode_data;
+
+    int index;
+    int display_mode_count;
+
+    screen_display_t      screen_display;
+    screen_display_mode_t *screen_display_modes;
+    int                   screen_format;
+    int                   screen_refresh_rate;
+
+    if (display_data == NULL) {
+        return false;
+    }
+    screen_display = display_data->screen_display;
+
+    /* create SDL display imodes based on display mode info from the display */
+    if (screen_get_display_property_iv(screen_display, SCREEN_PROPERTY_MODE_COUNT, &display_mode_count) < 0) {
+        return false;
+    }
+
+    screen_display_modes = SDL_calloc(display_mode_count, sizeof(screen_display_mode_t));
+    if (screen_display_modes == NULL) {
+        return false;
+    }
+
+    if(screen_get_display_modes(screen_display, display_mode_count, screen_display_modes) < 0) {
+        SDL_free(screen_display_modes);
+        return false;
+    }
+
+    for (index = 0; index < display_mode_count; index++) {
+        display_mode_data = (SDL_DisplayModeData *)SDL_calloc(1, sizeof(SDL_DisplayModeData));
+        if (display_mode_data == NULL) {
+            // Not much we can do about the objs we've already created at this point.
+            SDL_free(screen_display_modes);
+            return false;
+        }
+
+        SDL_zero(display_mode);
+        display_mode.w = screen_display_modes[index].width;
+        display_mode.h = screen_display_modes[index].height;
+        display_mode.pixel_density = 1.0;
+        display_mode.internal = display_mode_data;
+
+        if (screen_display_modes[index].flags & SCREEN_DISPLAY_MODE_REFRESH_VALID) {
+            screen_refresh_rate = screen_display_modes[index].refresh;
+        } else {
+            // Fallback
+            screen_refresh_rate = 60;
+        }
+        if (screen_display_modes[index].flags & SCREEN_DISPLAY_MODE_FORMAT_VALID) {
+            screen_format = screen_display_modes[index].format;
+        } else {
+            // Fallback
+            screen_format = SCREEN_FORMAT_RGBX8888;
+        }
+        display_mode.refresh_rate = screen_refresh_rate;
+        display_mode.format = screenToPixelFormat(screen_format);
+        display_mode_data->screen_format = screen_format;
+        display_mode_data->screen_display_mode = screen_display_modes[index];
+
+        // This op can fail if the mode already exists.
+        SDL_AddFullscreenDisplayMode(display, &display_mode);
+    }
+
+    SDL_free(screen_display_modes);
+
+    return true;
+}
+
+#if 0
+// FIXME: This seems to invalidate the screen_display_t, causing issues with the
+// (get|set)_display_property_*() apis. For now, mode switching is emulated
+// instead.
+bool setDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode)
+{
+    SDL_DisplayData     *display_data = display->internal;
+    SDL_DisplayModeData *display_mode_data = mode->internal;
+
+    if ((display_data == NULL) || (display_mode_data == NULL)) {
+        return false;
+    }
+
+    // TODO: May need to call glInitConfig and screen_create_window_buffers.
+    if (screen_set_display_property_iv(display_data->screen_display,
+        SCREEN_PROPERTY_MODE, (int *)&display_mode_data->screen_display_mode.index) < 0) {
+      

(Patch may be truncated, please check the link at the top of this post.)