From f86aa4a2b7b03f6220d2a1af8d71a0005895bcd9 Mon Sep 17 00:00:00 2001 From: baldurk Date: Fri, 5 Dec 2025 15:34:11 +0000 Subject: [PATCH] On windows/linux convert known native scancodes to default keys * This lets us have more helpful behaviour for default keys rather than being all-or-nothing, and display which keys are which. --- qrenderdoc/Windows/BufferViewer.cpp | 134 +------- .../Windows/Dialogs/CameraControlsDialog.cpp | 296 ++++++++++++++---- .../Windows/Dialogs/CameraControlsDialog.h | 2 + .../Windows/Dialogs/CameraControlsDialog.ui | 25 +- 4 files changed, 264 insertions(+), 193 deletions(-) diff --git a/qrenderdoc/Windows/BufferViewer.cpp b/qrenderdoc/Windows/BufferViewer.cpp index 7453e5676..0599cb72b 100644 --- a/qrenderdoc/Windows/BufferViewer.cpp +++ b/qrenderdoc/Windows/BufferViewer.cpp @@ -70,69 +70,6 @@ Q_DECLARE_METATYPE(FixedVarTag); static const uint32_t MaxVisibleRows = 10000; -namespace NativeScanCode -{ -enum -{ -#if defined(Q_OS_WIN32) - Key_A = 30, - Key_S = 31, - Key_D = 32, - Key_F = 33, - Key_W = 17, - Key_R = 19, -#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - Key_A = 30 + 8, - Key_S = 31 + 8, - Key_D = 32 + 8, - Key_F = 33 + 8, - Key_W = 17 + 8, - Key_R = 19 + 8, -#elif defined(Q_OS_MACOS) - // scan codes not supported on OS X - Key_A = 0xDEADBEF1, - Key_S = 0xDEADBEF2, - Key_D = 0xDEADBEF3, - Key_F = 0xDEADBEF4, - Key_W = 0xDEADBEF5, - Key_R = 0xDEADBEF6, -#else -#error "Unknown platform! Define NativeScanCode" -#endif -}; -}; // namespace NativeScanCode - -namespace NativeVirtualKey -{ -enum -{ -#if defined(Q_OS_WIN32) - Key_A = quint32('A'), - Key_S = quint32('S'), - Key_D = quint32('D'), - Key_F = quint32('F'), - Key_W = quint32('W'), - Key_R = quint32('R'), -#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - Key_A = quint32('a'), - Key_S = quint32('s'), - Key_D = quint32('d'), - Key_F = quint32('f'), - Key_W = quint32('w'), - Key_R = quint32('r'), -#elif defined(Q_OS_MACOS) - Key_A = 0x00, - Key_S = 0x01, - Key_D = 0x02, - Key_F = 0x03, - Key_W = 0x0D, - Key_R = 0x0F, -#else -#error "Unknown platform! Define NativeVirtualKey" -#endif -}; -}; // namespace NativeVirtualKey - class CameraWrapper { public: @@ -155,64 +92,27 @@ public: KeyPressDirection GetDirection(QKeyEvent *e) { - // if no settings are bound, use default bindings. This should be empty, but we also do this - // path if it's invalid and not the size we expect - if(m_Ctx.Config().MeshViewer_KeySettings.size() < (size_t)KeyPressDirection::NumSettings) + const rdcarray &keys = m_Ctx.Config().MeshViewer_KeySettings; + for(int i = 0; i < (int)KeyPressDirection::Count; i++) { - // if we have a native scancode, we expect to be able to match it. If we don't then don't get - // any false positives by checking the virtual key - if(e->nativeScanCode() > 1) - { - switch(e->nativeScanCode()) - { - case NativeScanCode::Key_A: return KeyPressDirection::Left; - case NativeScanCode::Key_D: return KeyPressDirection::Right; - case NativeScanCode::Key_W: return KeyPressDirection::Forward; - case NativeScanCode::Key_S: return KeyPressDirection::Back; - case NativeScanCode::Key_R: return KeyPressDirection::Up; - case NativeScanCode::Key_F: return KeyPressDirection::Down; - default: break; - } - } + KeyPressDirection dir = KeyPressDirection(i); + Qt::Key primary, secondary; + + int p = keySettingIdx(dir, true); + int s = keySettingIdx(dir, false); + + if(p < keys.count() && keys[p] != 0) + primary = getKeySetting(keys[p]); else - { - switch(e->nativeVirtualKey()) - { - case NativeVirtualKey::Key_A: return KeyPressDirection::Left; - case NativeVirtualKey::Key_D: return KeyPressDirection::Right; - case NativeVirtualKey::Key_W: return KeyPressDirection::Forward; - case NativeVirtualKey::Key_S: return KeyPressDirection::Back; - case NativeVirtualKey::Key_R: return KeyPressDirection::Up; - case NativeVirtualKey::Key_F: return KeyPressDirection::Down; - default: break; - } - } + primary = getDefaultKey(dir, true); - // handle arrow keys, we can do this safely with Qt::Key - switch(e->key()) - { - case Qt::Key_Left: return KeyPressDirection::Left; - case Qt::Key_Right: return KeyPressDirection::Right; - case Qt::Key_Up: return KeyPressDirection::Forward; - case Qt::Key_Down: return KeyPressDirection::Back; - case Qt::Key_PageUp: return KeyPressDirection::Up; - case Qt::Key_PageDown: return KeyPressDirection::Down; - default: break; - } - } - else - { - for(int i = 0; i < (int)KeyPressDirection::Count; i++) - { - KeyPressDirection dir = KeyPressDirection(i); - Qt::Key primary = - getKeySetting(m_Ctx.Config().MeshViewer_KeySettings[keySettingIdx(dir, true)]); - Qt::Key secondary = - getKeySetting(m_Ctx.Config().MeshViewer_KeySettings[keySettingIdx(dir, false)]); + if(s < keys.count() && keys[s] != 0) + secondary = getKeySetting(keys[s]); + else + secondary = getDefaultKey(dir, false); - if(e->key() == primary || e->key() == secondary) - return dir; - } + if(e->key() == primary || e->key() == secondary) + return dir; } return KeyPressDirection::None; diff --git a/qrenderdoc/Windows/Dialogs/CameraControlsDialog.cpp b/qrenderdoc/Windows/Dialogs/CameraControlsDialog.cpp index c04cdf4c0..8728a991f 100644 --- a/qrenderdoc/Windows/Dialogs/CameraControlsDialog.cpp +++ b/qrenderdoc/Windows/Dialogs/CameraControlsDialog.cpp @@ -38,14 +38,7 @@ CameraControlsDialog::CameraControlsDialog(ICaptureContext &Ctx, QWidget *parent ui->nearPlane->setValue(m_Ctx.Config().MeshViewer_CameraNear); ui->farPlane->setValue(m_Ctx.Config().MeshViewer_CameraFar); - if(m_Ctx.Config().MeshViewer_KeySettings.size() < (size_t)KeyPressDirection::NumSettings) - { - m_Keys.clear(); - } - else - { - m_Keys = m_Ctx.Config().MeshViewer_KeySettings; - } + m_Keys = m_Ctx.Config().MeshViewer_KeySettings; updateDisplayLabels(); @@ -80,7 +73,7 @@ CameraControlsDialog::CameraControlsDialog(ICaptureContext &Ctx, QWidget *parent QObject::connect(buttons, &QDialogButtonBox::rejected, m_KeybindDialog, &QDialog::reject); QLabel *instructions = new QLabel(m_KeybindDialog); - instructions->setText(tr("Press key or mouse button, or escape to clear binding.")); + instructions->setText(tr("Press key or mouse button, or escape to unbind.")); instructions->setWordWrap(true); QVBoxLayout *layout = new QVBoxLayout(m_KeybindDialog); @@ -96,20 +89,9 @@ CameraControlsDialog::CameraControlsDialog(ICaptureContext &Ctx, QWidget *parent void CameraControlsDialog::applyUpdatedControls() { - bool someSet = false; - m_Ctx.Config().MeshViewer_CameraNear = (float)ui->nearPlane->value(); m_Ctx.Config().MeshViewer_CameraFar = (float)ui->farPlane->value(); - for(size_t i = 0; i < m_Keys.size() && i < (size_t)KeyPressDirection::NumSettings; i++) - { - if(m_Keys[i]) - { - someSet = true; - break; - } - } - switch(ui->speedMod->currentIndex()) { case 0: m_Ctx.Config().MeshViewer_SpeedModifier = Qt::ShiftModifier; break; @@ -118,9 +100,6 @@ void CameraControlsDialog::applyUpdatedControls() default: m_Ctx.Config().MeshViewer_SpeedModifier = Qt::NoModifier; break; } - if(!someSet) - m_Keys.clear(); - m_Ctx.Config().MeshViewer_KeySettings = m_Keys; m_Ctx.Config().Save(); @@ -130,6 +109,7 @@ void CameraControlsDialog::applyUpdatedControls() void CameraControlsDialog::on_resetAll_clicked() { m_Keys.clear(); + m_Keys.resize((size_t)KeyPressDirection::NumSettings); updateDisplayLabels(); } @@ -287,17 +267,6 @@ QString CameraControlsDialog::wheelName(QPoint angleDelta) void CameraControlsDialog::updateDisplayLabels() { - bool someSet = false; - - for(size_t i = 0; i < m_Keys.size() && i < (size_t)KeyPressDirection::NumSettings; i++) - { - if(m_Keys[i]) - { - someSet = true; - break; - } - } - QLineEdit *labels[(size_t)KeyPressDirection::NumSettings] = { // KeyPressDirection::Forward, ui->forwardDisplay, @@ -319,45 +288,266 @@ void CameraControlsDialog::updateDisplayLabels() ui->downDisplay_2, }; - if(someSet) + for(size_t i = 0; i < (size_t)KeyPressDirection::Count; i++) { - for(size_t i = 0; i < (size_t)KeyPressDirection::NumSettings; i++) + for(size_t j = 0; j < 2; j++) { - if(m_Keys[i] == 0) + size_t idx = i * 2 + j; + + if(idx >= m_Keys.size() || m_Keys[idx] == 0) { - labels[i]->setText(tr("Unbound")); + labels[idx]->setText( + tr("Default - %1") + .arg(QKeySequence(getDefaultKey(KeyPressDirection(i), j == 0)).toString())); } else { QString displayString; - if(getKeySetting(m_Keys[i]) != Qt::Key_unknown) + if(getKeySetting(m_Keys[idx]) != Qt::Key_unknown) { - displayString = QKeySequence(getKeySetting(m_Keys[i])).toString(); + displayString = QKeySequence(getKeySetting(m_Keys[idx])).toString(); } - else if(getMouseButtonSetting(m_Keys[i]) != Qt::MaxMouseButton) + else if(getMouseButtonSetting(m_Keys[idx]) != Qt::MaxMouseButton) { - displayString = buttonName(getMouseButtonSetting(m_Keys[i])); + displayString = buttonName(getMouseButtonSetting(m_Keys[idx])); } - else if(getMouseWheelSetting(m_Keys[i]) != QPoint()) + else if(getMouseWheelSetting(m_Keys[idx]) != QPoint()) { - displayString = wheelName(getMouseWheelSetting(m_Keys[i])); + displayString = wheelName(getMouseWheelSetting(m_Keys[idx])); } - labels[i]->setText(displayString); + labels[idx]->setText(displayString); } } } - else - { - for(size_t i = 0; i < (size_t)KeyPressDirection::Count; i++) - { - labels[i * 2 + 0]->setText(tr("Default")); - labels[i * 2 + 1]->setText(QString()); - } - } } CameraControlsDialog::~CameraControlsDialog() { delete ui; } + +namespace NativeScanCode +{ +enum +{ +#if defined(Q_OS_WIN32) + Key_A = 30, + Key_S = 31, + Key_D = 32, + Key_F = 33, + Key_W = 17, + Key_R = 19, +#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) + Key_A = 30 + 8, + Key_S = 31 + 8, + Key_D = 32 + 8, + Key_F = 33 + 8, + Key_W = 17 + 8, + Key_R = 19 + 8, +#elif defined(Q_OS_MACOS) + // scan codes not supported on OS X + Key_A = 0xDEADBEF1, + Key_S = 0xDEADBEF2, + Key_D = 0xDEADBEF3, + Key_F = 0xDEADBEF4, + Key_W = 0xDEADBEF5, + Key_R = 0xDEADBEF6, +#else +#error "Unknown platform! Define NativeScanCode" +#endif +}; +}; // namespace NativeScanCode + +// default to wasd, this will not work with other keyboard layouts but is the only fallback we have +static Qt::Key defaultPrimaryKeys[(size_t)KeyPressDirection::Count] = { + Qt::Key_W, Qt::Key_S, Qt::Key_A, Qt::Key_D, Qt::Key_R, Qt::Key_F, +}; +// the default secondaries are fortunately fixed keys +static const Qt::Key defaultSecondaryKeys[(size_t)KeyPressDirection::Count] = { + Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_PageUp, Qt::Key_PageDown, +}; +static bool primaryKeysFilled = false; + +#if defined(Q_OS_WIN32) +#define NOMINMAX +#include + +void FetchDefaultPrimaryKeys() +{ + if(primaryKeysFilled) + return; + + primaryKeysFilled = true; + + static int scans[(size_t)KeyPressDirection::Count] = { + NativeScanCode::Key_W, NativeScanCode::Key_S, NativeScanCode::Key_A, + NativeScanCode::Key_D, NativeScanCode::Key_R, NativeScanCode::Key_F, + }; + + byte state[256] = {}; + for(size_t i = 0; i < (size_t)KeyPressDirection::Count; i++) + { + uint vk = MapVirtualKeyW(scans[i], MAPVK_VSC_TO_VK); + + wchar_t buf[8] = {}; + int res = ToUnicode(vk, scans[i], state, buf, 7, 0); + + if(res == 0) + { + qCritical() << "couldn't get key for" << i; + } + else + { + defaultPrimaryKeys[i] = Qt::Key(QChar(buf[0]).toUpper().unicode()); + } + } +} + +#elif defined(Q_OS_LINUX) + +#include +#include + +// predeclare enough of xkbcommon, so we don't have a new build time dependency on it. Qt will load it for us +extern "C" { +struct xkb_state; +struct xkb_context; +struct xkb_keymap; +typedef uint32_t xkb_keysym_t; +typedef uint32_t xkb_keycode_t; +enum xkb_context_flags +{ + XKB_CONTEXT_NO_DEFAULT_INCLUDES = 1, +}; +enum xkb_keymap_compile_flags +{ + XKB_KEYMAP_COMPILE_NO_FLAGS = 0, +}; + +xkb_context *xkb_context_new(xkb_context_flags flags); +int32_t xkb_x11_get_core_keyboard_device_id(xcb_connection_t *connection); +xkb_keymap *xkb_x11_keymap_new_from_device(xkb_context *context, xcb_connection_t *connection, + int32_t device_id, xkb_keymap_compile_flags flags); +xkb_state *xkb_x11_state_new_from_device(xkb_keymap *keymap, xcb_connection_t *connection, + int32_t device_id); +xkb_keysym_t xkb_state_key_get_one_sym(xkb_state *state, xkb_keycode_t key); +int xkb_keysym_to_utf8(xkb_keysym_t keysym, char *buffer, size_t size); +void xkb_state_unref(xkb_state *state); +void xkb_context_unref(xkb_context *context); +void xkb_keymap_unref(xkb_keymap *keymap); + +using PFN_xkb_context_new = decltype(&xkb_context_new); +using PFN_xkb_x11_get_core_keyboard_device_id = decltype(&xkb_x11_get_core_keyboard_device_id); +using PFN_xkb_x11_keymap_new_from_device = decltype(&xkb_x11_keymap_new_from_device); +using PFN_xkb_x11_state_new_from_device = decltype(&xkb_x11_state_new_from_device); +using PFN_xkb_state_key_get_one_sym = decltype(&xkb_state_key_get_one_sym); +using PFN_xkb_keysym_to_utf8 = decltype(&xkb_keysym_to_utf8); +using PFN_xkb_state_unref = decltype(&xkb_state_unref); +using PFN_xkb_context_unref = decltype(&xkb_context_unref); +using PFN_xkb_keymap_unref = decltype(&xkb_keymap_unref); +}; + +void *findXKBSym(const char *name) +{ + static void *searchLibs[] = { + RTLD_DEFAULT, + dlopen("libxkbcommon.so", RTLD_NOW | RTLD_LOCAL), + dlopen("libxkbcommon-x11.so", RTLD_NOW | RTLD_LOCAL), + dlopen("libxkbcommon-x11.so.0", RTLD_NOW | RTLD_LOCAL), + }; + + for(void *lib : searchLibs) + { + void *ret = dlsym(lib, name); + if(ret) + return ret; + } + + return NULL; +} + +void FetchDefaultPrimaryKeys() +{ + if(primaryKeysFilled) + return; + + primaryKeysFilled = true; + + PFN_xkb_context_new dyn_xkb_context_new = (PFN_xkb_context_new)findXKBSym("xkb_context_new"); + PFN_xkb_x11_get_core_keyboard_device_id dyn_xkb_x11_get_core_keyboard_device_id = + (PFN_xkb_x11_get_core_keyboard_device_id)findXKBSym("xkb_x11_get_core_keyboard_device_id"); + PFN_xkb_x11_keymap_new_from_device dyn_xkb_x11_keymap_new_from_device = + (PFN_xkb_x11_keymap_new_from_device)findXKBSym("xkb_x11_keymap_new_from_device"); + PFN_xkb_x11_state_new_from_device dyn_xkb_x11_state_new_from_device = + (PFN_xkb_x11_state_new_from_device)findXKBSym("xkb_x11_state_new_from_device"); + PFN_xkb_state_key_get_one_sym dyn_xkb_state_key_get_one_sym = + (PFN_xkb_state_key_get_one_sym)findXKBSym("xkb_state_key_get_one_sym"); + PFN_xkb_keysym_to_utf8 dyn_xkb_keysym_to_utf8 = + (PFN_xkb_keysym_to_utf8)findXKBSym("xkb_keysym_to_utf8"); + PFN_xkb_state_unref dyn_xkb_state_unref = (PFN_xkb_state_unref)findXKBSym("xkb_state_unref"); + PFN_xkb_context_unref dyn_xkb_context_unref = + (PFN_xkb_context_unref)findXKBSym("xkb_context_unref"); + PFN_xkb_keymap_unref dyn_xkb_keymap_unref = (PFN_xkb_keymap_unref)findXKBSym("xkb_keymap_unref"); + + // if both general and xcb symbols loaded, we're good to go + if(dyn_xkb_context_new && dyn_xkb_x11_keymap_new_from_device) + { + xcb_connection_t *connection = QX11Info::connection(); + xkb_context *context = dyn_xkb_context_new(XKB_CONTEXT_NO_DEFAULT_INCLUDES); + int core_device_id = dyn_xkb_x11_get_core_keyboard_device_id(connection); + xkb_keymap *keymap = dyn_xkb_x11_keymap_new_from_device(context, connection, core_device_id, + XKB_KEYMAP_COMPILE_NO_FLAGS); + xkb_state *state = dyn_xkb_x11_state_new_from_device(keymap, connection, core_device_id); + + static int scans[(size_t)KeyPressDirection::Count] = { + NativeScanCode::Key_W, NativeScanCode::Key_S, NativeScanCode::Key_A, + NativeScanCode::Key_D, NativeScanCode::Key_R, NativeScanCode::Key_F, + }; + + for(size_t i = 0; i < (size_t)KeyPressDirection::Count; i++) + { + xkb_keysym_t sym = dyn_xkb_state_key_get_one_sym(state, scans[i]); + + char buf[32] = {}; + int len = dyn_xkb_keysym_to_utf8(sym, buf, 31); + + if(len == 0) + { + qCritical() << "couldn't get key for" << i; + } + else + { + defaultPrimaryKeys[i] = Qt::Key(QString::fromUtf8(buf).unicode()->toUpper().unicode()); + } + } + + dyn_xkb_state_unref(state); + dyn_xkb_keymap_unref(keymap); + dyn_xkb_context_unref(context); + } +} + +#else + +void FetchDefaultPrimaryKeys() +{ + if(primaryKeysFilled) + return; + + primaryKeysFilled = true; + + qCritical() << "Unsupported platform to fetch default primary keys"; +} + +#endif + +Qt::Key getDefaultKey(KeyPressDirection dir, bool primary) +{ + FetchDefaultPrimaryKeys(); + + if(primary) + return defaultPrimaryKeys[(size_t)dir]; + + return defaultSecondaryKeys[(size_t)dir]; +} diff --git a/qrenderdoc/Windows/Dialogs/CameraControlsDialog.h b/qrenderdoc/Windows/Dialogs/CameraControlsDialog.h index 16a820f47..35cf7a711 100644 --- a/qrenderdoc/Windows/Dialogs/CameraControlsDialog.h +++ b/qrenderdoc/Windows/Dialogs/CameraControlsDialog.h @@ -50,6 +50,8 @@ constexpr int keySettingIdx(KeyPressDirection dir, bool primary) return int(dir) * 2 + (primary ? 0 : 1); } +Qt::Key getDefaultKey(KeyPressDirection dir, bool primary); + constexpr Qt::Key getKeySetting(uint32_t k) { return k != 0 && k < 0x10000000U ? Qt::Key(k) : Qt::Key_unknown; diff --git a/qrenderdoc/Windows/Dialogs/CameraControlsDialog.ui b/qrenderdoc/Windows/Dialogs/CameraControlsDialog.ui index 9542ccfec..3496cbb70 100644 --- a/qrenderdoc/Windows/Dialogs/CameraControlsDialog.ui +++ b/qrenderdoc/Windows/Dialogs/CameraControlsDialog.ui @@ -6,8 +6,8 @@ 0 0 - 686 - 356 + 654 + 288 @@ -100,27 +100,6 @@ 3 - - - - - 0 - 0 - - - - If any keys are set then only those will be active. - -If all keys are unbound then a default keymapping will be used - with WASD keys (ignoring local keyboard layout) for movement and R+F for vertical movement. - - - Qt::PlainText - - - true - - -