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.)