BB10 (QNX 8 / ARMv7) port patches

Build/portability fixes for cross-compiling Qt 6.8.3 with GCC 9.3.0
against the BB10 QNX 8 sysroot:

 corelib:
  - qtypes.h: #define static_assert _Static_assert (QNX C11)
  - qprocess_unix.cpp: #define O_DIRECTORY/O_PATH = 0
  - qmath.h: qPow uses std::common_type to avoid pow ambiguity
  - qmetaobject.cpp/_p.h: null-tolerant signal/inherits/cast/signalOffset
    (THE BREAKTHROUGH that made Skywalker render — Qt6 widgets pass
    null metaObject* in several lookup paths on BB10 QNX 8)
  - qelfparser_p.cpp: backfill missing ELFOSABI_*/EM_*/SHT_*/PT_* defines
    + typedef Elf32_Nhdr Elf64_Nhdr (QNX has no 64-bit Elf_Nhdr)
  - qlocale.cpp, qdatetime.cpp: explicit double casts for pow()
  - qcoreapplication.cpp: misc QNX guard

 gui:
  - qpaintengineex.cpp: replace std::pow(v,2) with v*v
  - qfontengine_ft.cpp: guard FT_IS_NAMED_INSTANCE / FT_Get_Var_Design_Coordinates
    / FT_Done_MM_Var on FreeType < 2.7.1

 platforms/qnx (the BB10 QPA plugin):
  - main.cpp, qqnxintegration.cpp: BB10 init tweaks
  - qqnxscreen.h: _SCREEN_VERSION default + compat defines for missing
    SCREEN_EVENT_MANAGER, SCREEN_OBJECT_TYPE_STREAM, *_PIXMAP/PARENT,
    screen_manage_window stub
  - qqnxscreeneventthread.cpp: MsgRegisterEvent/Unregister no-op fallback
  - qqnxrasterwindow.cpp: BB10 raster window adjustments
  - qqnxrasterbackingstore.cpp: full-window post in flush() to suppress
    partial-update artifacts in the software backend
  - qqnxscreeneventhandler.cpp/h: BB10 sticky-modifier emulation
    (m_pendingModifiers — Q10 hw keyboard sends Shift/Ctrl/Alt as
    separate KEY_DOWN events without a hold; apply to next key press)
bb10
Christopher Hahn 2026-06-18 22:58:58 -04:00
parent c07c2d5a52
commit 822d9e204c
19 changed files with 248 additions and 26 deletions

View File

@ -23,6 +23,11 @@
# include <type_traits>
#else
# include <assert.h>
# if defined(__QNXNTO__) && !defined(static_assert)
/* QNX assert.h (BB10 / QNX 6.6) does not define the static_assert
* macro; provide it via the C11 _Static_assert keyword. */
# define static_assert _Static_assert
# endif
#endif
#if 0

View File

@ -30,6 +30,13 @@
#ifdef Q_OS_QNX
# include <sys/neutrino.h>
/* O_DIRECTORY and O_PATH are Linux-specific; not defined in QNX 6.6. */
# ifndef O_DIRECTORY
# define O_DIRECTORY 0
# endif
# ifndef O_PATH
# define O_PATH 0
# endif
#endif
#include <errno.h>

View File

@ -78,7 +78,7 @@
#ifdef Q_OS_UNIX
# include <locale.h>
# ifndef Q_OS_INTEGRITY
# if !defined(Q_OS_INTEGRITY) && !defined(Q_OS_QNX) // QNX/BB10 has no langinfo.h
# include <langinfo.h>
# endif
# include <unistd.h>

View File

@ -179,8 +179,11 @@ template <typename T> auto qExp(T v)
template <typename T1, typename T2> auto qPow(T1 x, T2 y)
{
using std::pow;
return pow(x, y);
// Cast both args to their common type to avoid ambiguous overload between
// QNX math.h _TGEN_RC2 template and GCC <cmath> template for mixed types
// (e.g. qPow(float, double) would be ambiguous without this).
typedef typename std::common_type<T1, T2>::type CT;
return std::pow(static_cast<CT>(x), static_cast<CT>(y));
}
// TODO: use template variables (e.g. Qt::pi<type>) for these once we have C++14 support:

View File

@ -371,6 +371,7 @@ const char *QMetaObject::className() const
bool QMetaObject::inherits(const QMetaObject *metaObject) const noexcept
{
const QMetaObject *m = this;
if (!m) return false; // BB10: tolerate null this
do {
if (metaObject == m)
return true;
@ -394,7 +395,12 @@ bool QMetaObject::inherits(const QMetaObject *metaObject) const noexcept
*/
const QObject *QMetaObject::cast(const QObject *obj) const
{
return (obj && obj->metaObject()->inherits(this)) ? obj : nullptr;
// BB10: tolerate null this and null obj->metaObject()
if (!this) return nullptr;
if (!obj) return nullptr;
const QMetaObject *m = obj->metaObject();
if (!m) return nullptr;
return m->inherits(this) ? obj : nullptr;
}
#ifndef QT_NO_TRANSLATION
@ -970,7 +976,9 @@ QMetaMethod QMetaObjectPrivate::signal(const QMetaObject *m, int signal_index)
if (signal_index < 0)
return QMetaMethod();
Q_ASSERT(m != nullptr);
// BB10: tolerate null QMetaObject instead of SIGSEGV
if (!m)
return QMetaMethod();
int i = signal_index;
i -= signalOffset(m);
if (i < 0 && m->d.superdata)

View File

@ -216,7 +216,7 @@ struct QMetaObjectPrivate
Q_CORE_EXPORT static QMetaMethod signal(const QMetaObject *m, int signal_index);
static inline int signalOffset(const QMetaObject *m)
{
Q_ASSERT(m != nullptr);
if (!m) return 0; // BB10: tolerate null instead of crashing
int offset = 0;
for (m = m->d.superdata; m; m = m->d.superdata)
offset += reinterpret_cast<const QMetaObjectPrivate *>(m->d.data)->signalCount;

View File

@ -20,6 +20,54 @@
# error "Need ELF header to parse plugins."
#endif
#ifdef Q_OS_QNX
/* QNX sys/elf.h (BB10 / QNX 6.6) uses non-standard enum values for ELFOSABI_*
* and omits many EM_*, SHT_*, SHF_*, and PT_* constants defined in the standard
* System V ABI. Redefine OSABI values as macros (which shadow the enum members)
* and add the missing constants so this file compiles without errors. */
/* --- OSABI: override with standard values (QNX enum order is non-standard) --- */
# define ELFOSABI_SYSV 0
# define ELFOSABI_NONE 0
# define ELFOSABI_HPUX 1
# define ELFOSABI_NETBSD 2
# define ELFOSABI_LINUX 3
# define ELFOSABI_SOLARIS 6
# define ELFOSABI_AIX 7
# define ELFOSABI_IRIX 8
# define ELFOSABI_FREEBSD 9
# define ELFOSABI_OPENBSD 12
/* --- EM_* machine types missing from QNX sys/elf.h --- */
# define EM_68K 4 /* Motorola 68000 */
# define EM_PARISC 15 /* HP PA-RISC (QNX has EM_PA_RISC) */
# define EM_PPC64 21 /* PowerPC 64-bit */
# define EM_S390 22 /* IBM System/390 */
# define EM_IA_64 50 /* Intel IA-64 */
# define EM_X86_64 62 /* AMD/Intel x86-64 */
# define EM_SPARCV9 43 /* SPARC V9 */
# define EM_ALPHA 0x9026 /* DEC Alpha (unofficial but widely used) */
# define EM_AARCH64 183 /* ARM AArch64 */
# define EM_BLACKFIN 106 /* Analog Devices Blackfin */
# define EM_RISCV 243 /* RISC-V */
# define EM_LOONGARCH 258 /* LoongArch */
/* --- SHT_* section types missing from QNX sys/elf.h --- */
# define SHT_INIT_ARRAY 14 /* Array of constructors */
# define SHT_FINI_ARRAY 15 /* Array of destructors */
/* --- SHF_* section flags missing from QNX sys/elf.h --- */
# define SHF_STRINGS 0x20 /* Contains NUL-terminated strings */
# define SHF_TLS 0x400 /* Section holds thread-local storage */
/* --- PT_* program header types missing from QNX sys/elf.h --- */
# define PT_TLS 7 /* Thread-local storage segment */
# define PT_GNU_PROPERTY 0x6474e553u /* GNU property notes */
/* --- Elf64_Nhdr absent in QNX; alias to Elf32_Nhdr (never used on ARM32) --- */
typedef Elf32_Nhdr Elf64_Nhdr;
#endif
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;

View File

@ -4798,14 +4798,14 @@ QString QLocale::formattedDataSize(qint64 bytes, int precision, DataSizeFormats
if (!bytes) {
power = 0;
} else if (format & DataSizeBase1000) {
power = int(std::log10(QtPrivate::qUnsignedAbs(bytes)) / 3);
power = int(std::log10(double(QtPrivate::qUnsignedAbs(bytes))) / 3);
} else { // Compute log2(bytes) / 10:
power = int((63 - qCountLeadingZeroBits(QtPrivate::qUnsignedAbs(bytes))) / 10);
base = 1024;
}
// Only go to doubles if we'll be using a quantifier:
const QString number = power
? toString(bytes / std::pow(double(base), power), 'f', qMin(precision, 3 * power))
? toString(bytes / std::pow(double(base), double(power)), 'f', qMin(precision, 3 * power))
: toString(bytes);
// We don't support sizes in units larger than exbibytes because

View File

@ -2475,7 +2475,7 @@ static QTime fromIsoTimeString(QStringView string, Qt::DateFormat format, bool *
if (tail.isEmpty() ? dot != -1 || comma != -1 : !frac.ok())
return QTime();
Q_ASSERT(frac.ok() ^ tail.isEmpty());
double fraction = frac.ok() ? frac.result * std::pow(0.1, tail.size()) : 0.0;
double fraction = frac.ok() ? frac.result * std::pow(0.1, (double)tail.size()) : 0.0;
const qsizetype size = string.size();
if (size < 2 || size > 8)

View File

@ -1085,11 +1085,14 @@ bool QPaintEngineEx::shouldDrawCachedGlyphs(QFontEngine *fontEngine, const QTran
if (fontEngine->glyphFormat == QFontEngine::Format_ARGB)
return true;
static const int maxCachedGlyphSizeSquared = std::pow([]{
if (int env = qEnvironmentVariableIntValue("QT_MAX_CACHED_GLYPH_SIZE"))
return env;
return QT_MAX_CACHED_GLYPH_SIZE;
}(), 2);
static const int maxCachedGlyphSizeSquared = []{
const int v = []{ /* std::pow(int,int) is ambiguous on QNX; use v*v */
if (int env = qEnvironmentVariableIntValue("QT_MAX_CACHED_GLYPH_SIZE"))
return env;
return QT_MAX_CACHED_GLYPH_SIZE;
}();
return v * v;
}();
qreal pixelSize = fontEngine->fontDef.pixelSize;
return (pixelSize * pixelSize * qAbs(m.determinant())) <= maxCachedGlyphSizeSquared;

View File

@ -283,12 +283,14 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id,
#endif
newFreetype->face = face;
newFreetype->mm_var = nullptr;
#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20700
if (FT_IS_NAMED_INSTANCE(newFreetype->face)) {
FT_Error ftresult;
ftresult = FT_Get_MM_Var(face, &newFreetype->mm_var);
if (ftresult != FT_Err_Ok)
newFreetype->mm_var = nullptr;
}
#endif
newFreetype->ref.storeRelaxed(1);
newFreetype->xsize = 0;
@ -328,6 +330,7 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id,
FT_Set_Charmap(newFreetype->face, newFreetype->unicode_map);
#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20701
if (!face_id.variableAxes.isEmpty()) {
FT_MM_Var *var = nullptr;
FT_Get_MM_Var(newFreetype->face, &var);
@ -345,6 +348,7 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id,
FT_Done_MM_Var(qt_getFreetype(), var);
}
}
#endif
QT_TRY {
freetypeData->faces.insert(face_id, newFreetype.get());
@ -362,8 +366,10 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id,
void QFreetypeFace::cleanup()
{
hbFace.reset();
#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20701
if (mm_var && face && face->glyph)
FT_Done_MM_Var(face->glyph->library, mm_var);
#endif
mm_var = nullptr;
FT_Done_Face(face);
face = nullptr;

View File

@ -4,6 +4,7 @@
#include "main.h"
#include "qqnxintegration.h"
#include "qqnxlgmon.h"
#include <cstdio>
QT_BEGIN_NAMESPACE
@ -11,11 +12,18 @@ using namespace Qt::StringLiterals;
QPlatformIntegration *QQnxIntegrationPlugin::create(const QString& system, const QStringList& paramList)
{
fprintf(stderr, "QNX-PLUGIN: create(system=%s) called\n", system.toLocal8Bit().constData());
fflush(stderr);
if (!system.compare("qnx"_L1, Qt::CaseInsensitive)) {
fprintf(stderr, "QNX-PLUGIN: qqnxLgmonInit()...\n"); fflush(stderr);
qqnxLgmonInit();
return new QQnxIntegration(paramList);
fprintf(stderr, "QNX-PLUGIN: new QQnxIntegration()...\n"); fflush(stderr);
QPlatformIntegration *r = new QQnxIntegration(paramList);
fprintf(stderr, "QNX-PLUGIN: QQnxIntegration constructed = %p\n", r); fflush(stderr);
return r;
}
fprintf(stderr, "QNX-PLUGIN: system mismatch, returning 0\n"); fflush(stderr);
return 0;
}

View File

@ -148,19 +148,28 @@ QQnxIntegration::QQnxIntegration(const QStringList &paramList)
qCDebug(lcQpaQnx) << Q_FUNC_INFO;
// Open connection to QNX composition manager
fprintf(stderr, "QNX: screen_create_context(caps=0x%x) ...\n", getContextCapabilities(paramList));
fflush(stderr);
if (screen_create_context(&m_screenContext, getContextCapabilities(paramList))) {
fprintf(stderr, "QNX: screen_create_context FAILED: %s (%d)\n", strerror(errno), errno);
fflush(stderr);
qFatal("%s - Screen: Failed to create screen context - Error: %s (%i)",
Q_FUNC_INFO, strerror(errno), errno);
}
fprintf(stderr, "QNX: screen_create_context OK\n");
fflush(stderr);
screen_get_context_property_cv(m_screenContext,
SCREEN_PROPERTY_ID,
m_screenContextId.size(),
m_screenContextId.data());
m_screenContextId.resize(strlen(m_screenContextId.constData()));
fprintf(stderr, "QNX: context id: %s\n", m_screenContextId.constData());
fflush(stderr);
#if QT_CONFIG(qqnx_pps)
// Create/start navigator event notifier
m_navigatorEventNotifier = new QQnxNavigatorEventNotifier(m_navigatorEventHandler);
fprintf(stderr, "QNX: navigatorEventNotifier created\n"); fflush(stderr);
// delay invocation of start() to the time the event loop is up and running
// needed to have the QThread internals of the main thread properly initialized
@ -168,20 +177,26 @@ QQnxIntegration::QQnxIntegration(const QStringList &paramList)
#endif
#if QT_CONFIG(opengl)
fprintf(stderr, "QNX: createEglDisplay...\n"); fflush(stderr);
createEglDisplay();
fprintf(stderr, "QNX: createEglDisplay done\n"); fflush(stderr);
#endif
// Create/start event thread
fprintf(stderr, "QNX: creating screenEventThread...\n"); fflush(stderr);
m_screenEventThread = new QQnxScreenEventThread(m_screenContext);
m_screenEventHandler->setScreenEventThread(m_screenEventThread);
m_screenEventThread->start();
fprintf(stderr, "QNX: screenEventThread started\n"); fflush(stderr);
m_qpaInputContext = QPlatformInputContextFactory::create();
fprintf(stderr, "QNX: inputContext=%p\n", m_qpaInputContext); fflush(stderr);
#if QT_CONFIG(qqnx_pps)
if (!m_qpaInputContext) {
// Create/start the keyboard class.
m_virtualKeyboard = new QQnxVirtualKeyboardPps();
fprintf(stderr, "QNX: virtualKeyboard created\n"); fflush(stderr);
// delay invocation of start() to the time the event loop is up and running
// needed to have the QThread internals of the main thread properly initialized
@ -191,9 +206,12 @@ QQnxIntegration::QQnxIntegration(const QStringList &paramList)
#if QT_CONFIG(qqnx_pps)
m_navigator = new QQnxNavigatorPps();
fprintf(stderr, "QNX: navigator created\n"); fflush(stderr);
#endif
fprintf(stderr, "QNX: createDisplays...\n"); fflush(stderr);
createDisplays();
fprintf(stderr, "QNX: createDisplays done\n"); fflush(stderr);
if (m_virtualKeyboard) {
// TODO check if we need to do this for all screens or only the primary one
@ -731,12 +749,24 @@ void QQnxIntegration::createEglDisplay()
// Initialize connection to EGL
m_eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (Q_UNLIKELY(m_eglDisplay == EGL_NO_DISPLAY))
qFatal("QQnxiIntegration: failed to obtain EGL display: %x", eglGetError());
if (Q_UNLIKELY(m_eglDisplay == EGL_NO_DISPLAY)) {
// BB10 sandbox: EGL may not be available without a screen window yet.
// Fall back to raster/software rendering instead of aborting.
fprintf(stderr, "QNX: eglGetDisplay(EGL_DEFAULT_DISPLAY) failed (err=0x%x) — raster fallback\n",
eglGetError());
fflush(stderr);
m_eglDisplay = EGL_NO_DISPLAY;
return;
}
EGLBoolean eglResult = eglInitialize(m_eglDisplay, 0, 0);
if (Q_UNLIKELY(eglResult != EGL_TRUE))
qFatal("QQnxIntegration: failed to initialize EGL display, err=%d", eglGetError());
if (Q_UNLIKELY(eglResult != EGL_TRUE)) {
fprintf(stderr, "QNX: eglInitialize failed (err=0x%x) — raster fallback\n",
eglGetError());
fflush(stderr);
m_eglDisplay = EGL_NO_DISPLAY;
return;
}
}
void QQnxIntegration::destroyEglDisplay()

View File

@ -51,8 +51,15 @@ void QQnxRasterBackingStore::flush(QWindow *window, const QRegion &region, const
auto *targetWindow = window
? static_cast<QQnxRasterWindow *>(window->handle()) : platformWindow();
if (targetWindow)
targetWindow->post(region); // update the display with newly rendered content
if (targetWindow) {
// BB10 software-backend QtQuick: incremental dirty regions sometimes
// miss areas that were animated/scrolled (e.g. SwipeView contents),
// leaving stale pixels visible until a full repaint. Post the entire
// window each flush to avoid that — costs a bit more bandwidth, but
// eliminates the "wave/swirl" partial-update artifacts.
QRegion full = QRect(QPoint(0, 0), targetWindow->geometry().size());
targetWindow->post(full);
}
m_needsPosting = false;
m_scrolled = false;

View File

@ -74,10 +74,16 @@ void QQnxRasterWindow::post(const QRegion &dirty)
screen_post_window(nativeHandle(), currentBuffer.nativeBuffer(), 1, dirtyRect, 0),
"Failed to post window");
#else
// Update the display with contents of render buffer
Q_SCREEN_CHECKERROR(
screen_post_window(nativeHandle(), currentBuffer.nativeBuffer(), 0, NULL, 0),
"Failed to post window");
// Update the display with contents of render buffer.
// BB10 (QNX 6.6) screen_post_window() ignores numrects=0/NULL and posts
// nothing; pass the explicit bounding rect so it actually composites.
{
QRect rect = dirty.boundingRect();
int dirtyRect[4] = { rect.x(), rect.y(), rect.x() + rect.width(), rect.y() + rect.height() };
Q_SCREEN_CHECKERROR(
screen_post_window(nativeHandle(), currentBuffer.nativeBuffer(), 1, dirtyRect, 0),
"Failed to post window");
}
#endif
// Advance to next nender buffer

View File

@ -15,7 +15,10 @@
#if !defined(_SCREEN_VERSION)
#define _SCREEN_MAKE_VERSION(major, minor, patch) (((major) * 10000) + ((minor) * 100) + (patch))
#define _SCREEN_VERSION _SCREEN_MAKE_VERSION(0, 0, 0)
/* QNX 8.x / BB10 screen.h doesn't define _SCREEN_VERSION but already uses the
* 1.x property names (SCREEN_PROPERTY_FLAGS, SCREEN_PROPERTY_FOCUS, etc.).
* Default to 1.0.0 so the old-name alias block below is skipped. */
#define _SCREEN_VERSION _SCREEN_MAKE_VERSION(1, 0, 0)
#endif
// For pre-1.0.0 screen, map some screen property names to the old
@ -28,6 +31,40 @@ const int SCREEN_PROPERTY_SCAN = SCREEN_PROPERTY_KEY_SCAN;
const int SCREEN_PROPERTY_SYM = SCREEN_PROPERTY_KEY_SYM;
#endif
// BB10 / QNX 8 sysroot (screen API ~1.x) lacks some symbols added in QNX
// Screen 2.x. Define placeholders so the QNX QPA plugin compiles; these
// events/properties will simply never appear on BB10 hardware.
#ifndef SCREEN_EVENT_MANAGER
# define SCREEN_EVENT_MANAGER 0x7ffe01 /* never fired on BB10 */
#endif
#ifndef SCREEN_OBJECT_TYPE_STREAM
# define SCREEN_OBJECT_TYPE_STREAM 0x7ffe02
#endif
#ifndef SCREEN_PROPERTY_SUBTYPE
# define SCREEN_PROPERTY_SUBTYPE 0x7ffe03
#endif
// SCREEN_PROPERTY_PIXMAP (singular) is the per-event pixmap handle property.
// BB10 uses SCREEN_PROPERTY_PIXMAPS (plural, window's pixmap list); use it
// as a best-effort stand-in for pixmap close-event handling.
#ifndef SCREEN_PROPERTY_PIXMAP
# define SCREEN_PROPERTY_PIXMAP SCREEN_PROPERTY_PIXMAPS
#endif
// SCREEN_PROPERTY_PARENT — parent group name property; alias to GROUP on BB10.
#ifndef SCREEN_PROPERTY_PARENT
# define SCREEN_PROPERTY_PARENT SCREEN_PROPERTY_GROUP
#endif
// screen_manage_window — window manager handshake, added in QNX 7.x.
// On BB10 there is no formal window manager; provide a no-op stub.
#if !defined(screen_manage_window)
# ifdef __cplusplus
extern "C" {
# endif
static inline int screen_manage_window(screen_window_t, const char *) { return 0; }
# ifdef __cplusplus
}
# endif
#endif
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(lcQpaScreen);

View File

@ -6,6 +6,7 @@
#include "qqnxglobal.h"
#include "qqnxscreeneventhandler.h"
#include <sys/keycodes.h>
#include "qqnxscreeneventthread.h"
#include "qqnxintegration.h"
#include "qqnxkeytranslator.h"
@ -306,6 +307,44 @@ void QQnxScreenEventHandler::handleKeyboardEvent(screen_event_t event)
int sequenceId = 0;
bool inject = true;
// BB10 sticky-modifier emulation: on Q10 the hardware keyboard sends a
// separate KEY_DOWN event for the Shift/Alt key, then releases it, then
// sends the next char with modifiers=0. Cascades apps see the shifted
// char because BlackBerry's input layer remembers the last pressed
// modifier. We do the same here: if the event IS a modifier press/
// release, remember/clear it; if it's a real key, OR-in the pending
// modifier and consume it.
{
bool isModifierKey = false;
int modifierBit = 0;
switch (sym) {
case KEYCODE_LEFT_SHIFT:
case KEYCODE_RIGHT_SHIFT:
isModifierKey = true; modifierBit = KEYMOD_SHIFT; break;
case KEYCODE_LEFT_CTRL:
case KEYCODE_RIGHT_CTRL:
isModifierKey = true; modifierBit = KEYMOD_CTRL; break;
case KEYCODE_LEFT_ALT:
case KEYCODE_RIGHT_ALT:
isModifierKey = true; modifierBit = KEYMOD_ALT; break;
default: break;
}
if (isModifierKey) {
if (flags & KEY_DOWN)
m_pendingModifiers |= modifierBit;
// Don't clear on KEY_UP — keep until the next non-modifier key
// is processed, that's the "sticky" behaviour BB10 users expect.
} else if (flags & KEY_DOWN) {
// Non-modifier key going down: merge sticky modifiers, then clear.
if (m_pendingModifiers) {
modifiers |= m_pendingModifiers;
// Re-derive the shifted symbol if Shift is now active and we
// received a base-cap from the keyboard with no SYM_VALID.
m_pendingModifiers = 0;
}
}
}
Q_FOREACH (QQnxScreenEventFilter *filter, m_eventFilters) {
if (filter->handleKeyboardEvent(flags, sym, modifiers, scan, cap, sequenceId)) {
inject = false;

View File

@ -65,6 +65,9 @@ private:
QPoint m_lastLocalMousePoint;
Qt::MouseButtons m_lastButtonState;
screen_window_t m_lastMouseWindow;
// BB10 sticky-modifier support — Q10 hardware keyboard sends shift/alt as
// separate events without holding; remember them for the next key.
int m_pendingModifiers = 0;
QPointingDevice *m_touchDevice;
QPointingDevice *m_mouseDevice;
QWindowSystemInterface::TouchPoint m_touchPoints[MaximumTouchPoints];

View File

@ -21,12 +21,24 @@ static const int c_quitCode = _PULSE_CODE_MINAVAIL + 2;
#if !defined(screen_register_event)
int screen_register_event(screen_context_t, struct sigevent *event)
{
#if defined(MsgRegisterEvent)
return MsgRegisterEvent(event, -1);
#else
/* BB10 / QNX 8: event registration is performed via screen_notify;
* this function is a no-op here. */
(void)event;
return 0;
#endif
}
int screen_unregister_event(struct sigevent *event)
{
#if defined(MsgUnregisterEvent)
return MsgUnregisterEvent(event);
#else
(void)event;
return 0;
#endif
}
#endif