This commit is contained in:
y5nw 2024-08-12 01:24:51 +02:00
parent 246c35316c
commit cd8dee2779
10 changed files with 219 additions and 158 deletions

View File

@ -342,6 +342,26 @@ public:
{
return video::isDriverSupported(driver);
}
// This is a trivial (near-identity) mapping for converting between scancodes and keycodes for devices that do
// not implement this.
//! Get the scancode of the corresponding keycode.
virtual u32 getScancodeFromKey(const KeyCode &key) const
{
return key.index() == 0 ? std::get<EKEY_CODE>(key) : KEY_KEY_CODES_COUNT + std::get<wchar_t>(key);
}
//! Get the keycode of the corresponding scancode.
virtual KeyCode getKeyFromScancode(const u32 scancode) const
{
KeyCode key;
if (scancode < KEY_KEY_CODES_COUNT)
key.emplace<EKEY_CODE>((EKEY_CODE)scancode);
else
key.emplace<wchar_t>(scancode - KEY_KEY_CODES_COUNT);
return key;
}
};
} // end namespace irr

View File

@ -3,6 +3,7 @@
// For conditions of distribution and use, see copyright notice in irrlicht.h
#pragma once
#include <variant>
namespace irr
{
@ -182,4 +183,29 @@ enum EKEY_CODE
KEY_KEY_CODES_COUNT = 0x100 // this is not a key, but the amount of keycodes there are.
};
class KeyCode : public std::variant<EKEY_CODE, wchar_t> {
using super = std::variant<EKEY_CODE, wchar_t>;
public:
KeyCode() : KeyCode(KEY_KEY_CODES_COUNT, L'\0') {}
KeyCode(EKEY_CODE code, wchar_t ch)
{
emplace(code, ch);
}
using super::emplace;
void emplace(EKEY_CODE code, wchar_t ch)
{
if (isValid(code))
emplace<EKEY_CODE>(code);
else
emplace<wchar_t>(ch);
}
static bool isValid(EKEY_CODE code)
{
return code > 0 && code < KEY_KEY_CODES_COUNT;
}
};
} // end namespace irr

View File

@ -220,6 +220,31 @@ int CIrrDeviceSDL::findCharToPassToIrrlicht(uint32_t sdlKey, EKEY_CODE irrlichtK
}
}
u32 CIrrDeviceSDL::getScancodeFromKey(const KeyCode &key) const
{
u32 keynum = 0;
if (key.index() == 0) {
auto keycode = std::get<EKEY_CODE>(key);
for (const auto &entry: KeyMap) {
if (entry.second == keycode) {
keynum = entry.first;
break;
}
}
} else {
keynum = std::get<wchar_t>(key);
}
return SDL_GetScancodeFromKey(keynum);
}
KeyCode CIrrDeviceSDL::getKeyFromScancode(const u32 scancode) const
{
auto keycode = SDL_GetKeyFromScancode((SDL_Scancode)scancode);
const auto &keyentry = KeyMap.find(keycode);
auto irrcode = keyentry != KeyMap.end() ? keyentry->second : KEY_UNKNOWN;
return KeyCode(irrcode, keycode);
}
void CIrrDeviceSDL::resetReceiveTextInputEvents()
{
gui::IGUIElement *elem = GUIEnvironment->getFocus();
@ -815,6 +840,21 @@ bool CIrrDeviceSDL::run()
case SDL_KEYDOWN:
case SDL_KEYUP: {
auto keysym = SDL_event.key.keysym.sym;
auto scancode = SDL_event.key.keysym.scancode;
// Treat AC_BACK as the Escape key
if (scancode == SDL_SCANCODE_AC_BACK || scancode == SDL_SCANCODE_ESCAPE)
{
if (SDL_event.type == SDL_KEYDOWN)
escapeKeys.insert(scancode);
else
escapeKeys.erase(scancode);
if (SDL_event.type == SDL_KEYUP && !escapeKeys.empty())
break; // avoid sending KEYUP twice if AC_BACK and ESCAPE are both released
scancode = SDL_SCANCODE_ESCAPE;
keysym = SDLK_ESCAPE;
}
const auto &entry = KeyMap.find(keysym);
auto key = entry == KeyMap.end() ? KEY_UNKNOWN : entry->second;
@ -832,6 +872,8 @@ bool CIrrDeviceSDL::run()
irrevent.KeyInput.Control = (SDL_event.key.keysym.mod & KMOD_CTRL) != 0;
irrevent.KeyInput.Char = findCharToPassToIrrlicht(keysym, key,
(SDL_event.key.keysym.mod & KMOD_NUM) != 0);
irrevent.KeyInput.SystemKeyCode = scancode;
postEventFromUser(irrevent);
} break;
@ -1298,9 +1340,6 @@ void CIrrDeviceSDL::createKeyMap()
// buttons missing
// Android back button = ESC
KeyMap.emplace(SDLK_AC_BACK, KEY_ESCAPE);
KeyMap.emplace(SDLK_BACKSPACE, KEY_BACK);
KeyMap.emplace(SDLK_TAB, KEY_TAB);
KeyMap.emplace(SDLK_CLEAR, KEY_CLEAR);

View File

@ -24,6 +24,8 @@
#include <SDL_syswm.h>
#include <memory>
#include <unordered_map>
#include <unordered_set>
namespace irr
{
@ -286,6 +288,9 @@ private:
// Return the Char that should be sent to Irrlicht for the given key (either the one passed in or 0).
static int findCharToPassToIrrlicht(uint32_t sdlKey, EKEY_CODE irrlichtKey, bool numlock);
virtual u32 getScancodeFromKey(const KeyCode &key) const override;
virtual KeyCode getKeyFromScancode(const u32 scancode) const override;
// Check if a text box is in focus. Enable or disable SDL_TEXTINPUT events only if in focus.
void resetReceiveTextInputEvents();
@ -324,6 +329,8 @@ private:
s32 CurrentTouchCount;
bool IsInBackground;
std::unordered_set<SDL_Scancode> escapeKeys;
};
} // end namespace irr

View File

@ -143,7 +143,7 @@ bool MyEventReceiver::OnEvent(const SEvent &event)
// Remember whether each key is down or up
if (event.EventType == irr::EET_KEY_INPUT_EVENT) {
const KeyPress keyCode(event.KeyInput);
if (keysListenedFor[keyCode]) {
if (keyCode && keysListenedFor[keyCode]) {
if (event.KeyInput.PressedDown) {
if (!IsKeyDown(keyCode))
keyWasPressed.set(keyCode);

View File

@ -6,15 +6,17 @@
#include "settings.h"
#include "log.h"
#include "debug.h"
#include "renderingengine.h"
#include "util/hex.h"
#include "util/string.h"
#include "util/basic_macros.h"
#include <vector>
struct table_key {
const char *Name;
std::string Name;
irr::EKEY_CODE Key;
wchar_t Char; // L'\0' means no character assigned
const char *LangName; // NULL means it doesn't have a human description
std::string LangName; // NULL means it doesn't have a human description
};
#define DEFINEKEY1(x, lang) /* Irrlicht key without character */ \
@ -30,7 +32,7 @@ struct table_key {
#define N_(text) text
static const struct table_key table[] = {
static std::vector<table_key> table = {
// Keys that can be reliably mapped between Char and Key
DEFINEKEY3(0)
DEFINEKEY3(1)
@ -126,7 +128,7 @@ static const struct table_key table[] = {
DEFINEKEY1(KEY_ADD, N_("Numpad +"))
DEFINEKEY1(KEY_SEPARATOR, N_("Numpad ."))
DEFINEKEY1(KEY_SUBTRACT, N_("Numpad -"))
DEFINEKEY1(KEY_DECIMAL, NULL)
DEFINEKEY1(KEY_DECIMAL, N_("Numpad ."))
DEFINEKEY1(KEY_DIVIDE, N_("Numpad /"))
DEFINEKEY4(1)
DEFINEKEY4(2)
@ -221,33 +223,16 @@ static const struct table_key table[] = {
DEFINEKEY5("_")
};
static const table_key invalid_key = {"", irr::KEY_UNKNOWN, L'\0', ""};
#undef N_
static const table_key &lookup_keyname(const char *name)
{
for (const auto &table_key : table) {
if (strcmp(table_key.Name, name) == 0)
return table_key;
}
throw UnknownKeycode(name);
}
static const table_key &lookup_keykey(irr::EKEY_CODE key)
{
for (const auto &table_key : table) {
if (table_key.Key == key)
return table_key;
}
std::ostringstream os;
os << "<Keycode " << (int) key << ">";
throw UnknownKeycode(os.str().c_str());
}
static const table_key &lookup_keychar(wchar_t Char)
{
if (Char == L'\0')
return invalid_key;
for (const auto &table_key : table) {
if (table_key.Char == Char)
return table_key;
@ -255,88 +240,73 @@ static const table_key &lookup_keychar(wchar_t Char)
std::ostringstream os;
os << "<Char " << hex_encode((char*) &Char, sizeof(wchar_t)) << ">";
throw UnknownKeycode(os.str().c_str());
auto newsym = wide_to_utf8(std::wstring_view(&Char, 1));
table_key new_key = {newsym, irr::KEY_KEY_CODES_COUNT, Char, newsym};
return table.emplace_back(std::move(new_key));
}
KeyPress::KeyPress(const char *name)
static const table_key &lookup_keyname(const std::string_view &name)
{
if (strlen(name) == 0) {
Key = irr::KEY_KEY_CODES_COUNT;
Char = L'\0';
m_name = "";
return;
if (name.empty())
return invalid_key;
for (const auto &table_key : table) {
if (table_key.Name == name)
return table_key;
}
if (strlen(name) <= 4) {
// Lookup by resulting character
int chars_read = mbtowc(&Char, name, 1);
FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character");
try {
auto &k = lookup_keychar(Char);
m_name = k.Name;
Key = k.Key;
return;
} catch (UnknownKeycode &e) {};
} else {
// Lookup by name
m_name = name;
try {
auto &k = lookup_keyname(name);
Key = k.Key;
Char = k.Char;
return;
} catch (UnknownKeycode &e) {};
auto wname = utf8_to_wide(name);
if (wname.empty())
return invalid_key;
return lookup_keychar(wname[0]);
}
static const table_key &lookup_keykey(irr::EKEY_CODE key)
{
if (!KeyCode::isValid(key))
return invalid_key;
for (const auto &table_key : table) {
if (table_key.Key == key)
return table_key;
}
// It's not a known key, complain and try to do something
Key = irr::KEY_KEY_CODES_COUNT;
int chars_read = mbtowc(&Char, name, 1);
FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character");
m_name = "";
warningstream << "KeyPress: Unknown key '" << name
<< "', falling back to first char." << std::endl;
std::ostringstream os;
os << "<Keycode " << (int) key << ">";
return invalid_key;
}
KeyPress::KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character)
static const table_key &lookup_scancode(const u32 scancode)
{
if (prefer_character)
Key = irr::KEY_KEY_CODES_COUNT;
else
Key = in.Key;
Char = in.Char;
try {
if (valid_kcode(Key))
m_name = lookup_keykey(Key).Name;
else
m_name = lookup_keychar(Char).Name;
} catch (UnknownKeycode &e) {
m_name.clear();
};
auto key = RenderingEngine::get_raw_device()->getKeyFromScancode(scancode);
return key.index() == 0 ? lookup_keykey(std::get<irr::EKEY_CODE>(key)) : lookup_keychar(std::get<wchar_t>(key));
}
const char *KeyPress::sym() const
KeyPress::KeyPress(const std::string_view &name)
{
return m_name.c_str();
const auto &key = lookup_keyname(name);
KeyCode keycode(key.Key, key.Char);
scancode = RenderingEngine::get_raw_device()->getScancodeFromKey(keycode);
}
const char *KeyPress::name() const
std::string KeyPress::sym() const
{
if (m_name.empty())
return "";
const char *ret;
if (valid_kcode(Key))
ret = lookup_keykey(Key).LangName;
else
ret = lookup_keychar(Char).LangName;
return ret ? ret : "<Unnamed key>";
return lookup_scancode(scancode).Name;
}
const KeyPress EscapeKey("KEY_ESCAPE");
std::string KeyPress::name() const
{
return lookup_scancode(scancode).LangName;
}
const KeyPress LMBKey("KEY_LBUTTON");
const KeyPress MMBKey("KEY_MBUTTON");
const KeyPress RMBKey("KEY_RBUTTON");
std::unordered_map<std::string, KeyPress> KeyPress::specialKeyCache;
const KeyPress &KeyPress::getSpecialKey(const std::string &name)
{
auto &key = specialKeyCache[name];
if (!key)
key = KeyPress(name);
return key;
}
/*
Key config
@ -352,7 +322,7 @@ const KeyPress &getKeySetting(const char *settingname)
return n->second;
auto &ref = g_key_setting_cache[settingname];
ref = g_settings->get(settingname).c_str();
ref = std::string_view(g_settings->get(settingname));
return ref;
}

View File

@ -4,57 +4,49 @@
#pragma once
#include "exceptions.h"
#include "irrlichttypes.h"
#include <Keycodes.h>
#include <IEventReceiver.h>
#include <string>
#include <unordered_map>
class UnknownKeycode : public BaseException
{
public:
UnknownKeycode(const char *s) :
BaseException(s) {};
};
/* A key press, consisting of either an Irrlicht keycode
or an actual char */
/* A key press, consisting of a scancode or a keycode */
class KeyPress
{
public:
KeyPress() = default;
KeyPress() {};
KeyPress(const char *name);
KeyPress(const std::string_view &name);
KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character = false);
KeyPress(const irr::SEvent::SKeyInput &in) :
scancode(in.SystemKeyCode) {};
bool operator==(const KeyPress &o) const
{
return (Char > 0 && Char == o.Char) || (valid_kcode(Key) && Key == o.Key);
std::string sym() const;
std::string name() const;
bool operator==(const KeyPress &o) const {
return scancode == o.scancode;
}
const char *sym() const;
const char *name() const;
protected:
static bool valid_kcode(irr::EKEY_CODE k)
{
return k > 0 && k < irr::KEY_KEY_CODES_COUNT;
operator bool() const {
return scancode != 0;
}
irr::EKEY_CODE Key = irr::KEY_KEY_CODES_COUNT;
wchar_t Char = L'\0';
std::string m_name = "";
static const KeyPress &getSpecialKey(const std::string &name);
private:
u32 scancode = 0;
static std::unordered_map<std::string, KeyPress> specialKeyCache;
};
// Global defines for convenience
extern const KeyPress EscapeKey;
extern const KeyPress LMBKey;
extern const KeyPress MMBKey; // Middle Mouse Button
extern const KeyPress RMBKey;
// This implementation defers creation of the objects to make sure that the
// IrrlichtDevice is initialized.
#define EscapeKey KeyPress::getSpecialKey("KEY_ESCAPE")
#define LMBKey KeyPress::getSpecialKey("KEY_LBUTTON")
#define MMBKey KeyPress::getSpecialKey("KEY_MBUTTON") // Middle Mouse Button
#define RMBKey KeyPress::getSpecialKey("KEY_RBUTTON")
// Key configuration getter
const KeyPress &getKeySetting(const char *settingname);

View File

@ -253,8 +253,7 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
if (event.EventType == EET_KEY_INPUT_EVENT && active_key
&& event.KeyInput.PressedDown) {
bool prefer_character = shift_down;
KeyPress kp(event.KeyInput, prefer_character);
KeyPress kp(event.KeyInput);
if (event.KeyInput.Key == irr::KEY_DELETE)
kp = KeyPress(""); // To erase key settings
@ -270,7 +269,7 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
// Display Key already in use message
bool key_in_use = false;
if (strcmp(kp.sym(), "") != 0) {
if (kp) {
for (key_setting *ks : key_settings) {
if (ks != active_key && ks->key == kp) {
key_in_use = true;

View File

@ -190,13 +190,7 @@ static EKEY_CODE id_to_keycode(touch_gui_button_id id)
}
assert(!key.empty());
std::string resolved = g_settings->get("keymap_" + key);
try {
code = keyname_to_keycode(resolved.c_str());
} catch (UnknownKeycode &e) {
code = KEY_UNKNOWN;
warningstream << "TouchControls: Unknown key '" << resolved
<< "' for '" << key << "', hiding button." << std::endl;
}
code = keyname_to_keycode(resolved.c_str());
return code;
}

View File

@ -7,6 +7,7 @@
#include <string>
#include "exceptions.h"
#include "client/keycode.h"
#include "client/renderingengine.h" // scancode<->keycode conversion
class TestKeycode : public TestBase {
public:
@ -24,42 +25,56 @@ static TestKeycode g_test_instance;
void TestKeycode::runTests(IGameDef *gamedef)
{
// TODO: How do we test this without an IrrlichtDevice?
#if 0
TEST(testCreateFromString);
TEST(testCreateFromSKeyInput);
TEST(testCompare);
#endif
}
////////////////////////////////////////////////////////////////////////////////
#define UASSERTEQ_STR(one, two) UASSERT(strcmp(one, two) == 0)
#define UASSERTEQ_STR(one, two) UASSERT(one == two)
#define UASSERT_HAS_NAME(k) UASSERT(!k.name().empty())
void TestKeycode::testCreateFromString()
{
KeyPress k;
k = KeyPress("");
UASSERTEQ_STR(k.sym(), "");
UASSERTEQ_STR(k.name(), "");
// Character key, from char
k = KeyPress("R");
UASSERTEQ_STR(k.sym(), "KEY_KEY_R");
UASSERTCMP(int, >, strlen(k.name()), 0); // should have human description
UASSERT_HAS_NAME(k);
// Character key, from identifier
k = KeyPress("KEY_KEY_B");
UASSERTEQ_STR(k.sym(), "KEY_KEY_B");
UASSERTCMP(int, >, strlen(k.name()), 0);
UASSERT_HAS_NAME(k);
// Non-Character key, from identifier
k = KeyPress("KEY_UP");
UASSERTEQ_STR(k.sym(), "KEY_UP");
UASSERTCMP(int, >, strlen(k.name()), 0);
UASSERT_HAS_NAME(k);
k = KeyPress("KEY_F6");
UASSERTEQ_STR(k.sym(), "KEY_F6");
UASSERTCMP(int, >, strlen(k.name()), 0);
UASSERT_HAS_NAME(k);
// Irrlicht-unknown key, from char
k = KeyPress("/");
UASSERTEQ_STR(k.sym(), "/");
UASSERTCMP(int, >, strlen(k.name()), 0);
UASSERT_HAS_NAME(k);
}
template<typename ...Args>
static u32 toScancode(Args... args)
{
return RenderingEngine::get_raw_device()->getScancodeFromKey(KeyCode(args...));
}
void TestKeycode::testCreateFromSKeyInput()
@ -68,47 +83,46 @@ void TestKeycode::testCreateFromSKeyInput()
irr::SEvent::SKeyInput in;
// Character key
in.Key = irr::KEY_KEY_3;
in.Char = L'3';
in.SystemKeyCode = toScancode(irr::KEY_KEY_3, L'3');
k = KeyPress(in);
UASSERTEQ_STR(k.sym(), "KEY_KEY_3");
UASSERT_HAS_NAME(k);
// Non-Character key
in.Key = irr::KEY_RSHIFT;
in.Char = L'\0';
in.SystemKeyCode = toScancode(irr::KEY_RSHIFT, L'\0');
k = KeyPress(in);
UASSERTEQ_STR(k.sym(), "KEY_RSHIFT");
UASSERT_HAS_NAME(k);
// Irrlicht-unknown key
in.Key = irr::KEY_KEY_CODES_COUNT;
in.Char = L'?';
in.SystemKeyCode = toScancode(KEY_KEY_CODES_COUNT, L'?');
k = KeyPress(in);
UASSERTEQ_STR(k.sym(), "?");
// prefer_character mode
in.Key = irr::KEY_COMMA;
in.Char = L'G';
k = KeyPress(in, true);
UASSERTEQ_STR(k.sym(), "KEY_KEY_G");
UASSERT_HAS_NAME(k);
}
void TestKeycode::testCompare()
{
// "Empty" key
UASSERT(KeyPress() == KeyPress(""));
// Basic comparison
UASSERT(KeyPress("5") == KeyPress("KEY_KEY_5"));
UASSERT(!(KeyPress("5") == KeyPress("KEY_NUMPAD5")));
// Matching char suffices
// note: This is a real-world example, Irrlicht maps XK_equal to irr::KEY_PLUS on Linux
// TODO: Is this still relevant for scancodes?
irr::SEvent::SKeyInput in;
/*
in.Key = irr::KEY_PLUS;
in.Char = L'=';
UASSERT(KeyPress("=") == KeyPress(in));
*/
// Matching keycode suffices
irr::SEvent::SKeyInput in2;
in.Key = in2.Key = irr::KEY_OEM_CLEAR;
in.Char = L'\0';
in2.Char = L';';
in.SystemKeyCode = toScancode(irr::KEY_OEM_CLEAR, L'\0');
in2.SystemKeyCode = toScancode(irr::KEY_OEM_CLEAR, L';');
UASSERT(KeyPress(in) == KeyPress(in2));
}