Add InputEventKey.location to tell left from right

This adds a new enum `KeyLocation` and associated property
`InputEventKey.location`, which indicates the left/right location of key
events which may come from one of two physical keys, eg. Shift, Ctrl.

It also adds simulation of missing Shift KEYUP events for Windows.
When multiple Shifts are held down at the same time, Windows natively
only sends a KEYUP for the last one to be released.
This commit is contained in:
Mel Collins 2023-08-03 15:18:26 +02:00
parent 4b6ad34988
commit 8406e60522
35 changed files with 397 additions and 26 deletions

View file

@ -507,6 +507,10 @@ void register_global_constants() {
BIND_CORE_BITFIELD_CLASS_FLAG(KeyModifierMask, KEY_MASK, KPAD);
BIND_CORE_BITFIELD_CLASS_FLAG(KeyModifierMask, KEY_MASK, GROUP_SWITCH);
BIND_CORE_ENUM_CLASS_CONSTANT(KeyLocation, KEY_LOCATION, UNSPECIFIED);
BIND_CORE_ENUM_CLASS_CONSTANT(KeyLocation, KEY_LOCATION, LEFT);
BIND_CORE_ENUM_CLASS_CONSTANT(KeyLocation, KEY_LOCATION, RIGHT);
BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, NONE);
BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, LEFT);
BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, RIGHT);

View file

@ -364,6 +364,15 @@ char32_t InputEventKey::get_unicode() const {
return unicode;
}
void InputEventKey::set_location(KeyLocation p_key_location) {
location = p_key_location;
emit_changed();
}
KeyLocation InputEventKey::get_location() const {
return location;
}
void InputEventKey::set_echo(bool p_enable) {
echo = p_enable;
emit_changed();
@ -436,6 +445,23 @@ String InputEventKey::as_text_key_label() const {
return mods_text.is_empty() ? kc : mods_text + "+" + kc;
}
String InputEventKey::as_text_location() const {
String loc;
switch (location) {
case KeyLocation::LEFT:
loc = "left";
break;
case KeyLocation::RIGHT:
loc = "right";
break;
default:
break;
}
return loc;
}
String InputEventKey::as_text() const {
String kc;
@ -464,6 +490,11 @@ String InputEventKey::to_string() {
String kc = "";
String physical = "false";
String loc = as_text_location();
if (loc.is_empty()) {
loc = "unspecified";
}
if (keycode == Key::NONE && physical_keycode == Key::NONE && unicode != 0) {
kc = "U+" + String::num_uint64(unicode, 16) + " (" + String::chr(unicode) + ")";
} else if (keycode != Key::NONE) {
@ -478,7 +509,7 @@ String InputEventKey::to_string() {
String mods = InputEventWithModifiers::as_text();
mods = mods.is_empty() ? "none" : mods;
return vformat("InputEventKey: keycode=%s, mods=%s, physical=%s, pressed=%s, echo=%s", kc, mods, physical, p, e);
return vformat("InputEventKey: keycode=%s, mods=%s, physical=%s, location=%s, pressed=%s, echo=%s", kc, mods, physical, loc, p, e);
}
Ref<InputEventKey> InputEventKey::create_reference(Key p_keycode, bool p_physical) {
@ -531,6 +562,9 @@ bool InputEventKey::action_match(const Ref<InputEvent> &p_event, bool p_exact_ma
match = keycode == key->keycode;
} else if (physical_keycode != Key::NONE) {
match = physical_keycode == key->physical_keycode;
if (location != KeyLocation::UNSPECIFIED) {
match &= location == key->location;
}
} else {
match = false;
}
@ -572,6 +606,9 @@ bool InputEventKey::is_match(const Ref<InputEvent> &p_event, bool p_exact_match)
return (keycode == key->keycode) &&
(!p_exact_match || get_modifiers_mask() == key->get_modifiers_mask());
} else if (physical_keycode != Key::NONE) {
if (location != KeyLocation::UNSPECIFIED && location != key->location) {
return false;
}
return (physical_keycode == key->physical_keycode) &&
(!p_exact_match || get_modifiers_mask() == key->get_modifiers_mask());
} else {
@ -594,6 +631,9 @@ void InputEventKey::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_unicode", "unicode"), &InputEventKey::set_unicode);
ClassDB::bind_method(D_METHOD("get_unicode"), &InputEventKey::get_unicode);
ClassDB::bind_method(D_METHOD("set_location", "location"), &InputEventKey::set_location);
ClassDB::bind_method(D_METHOD("get_location"), &InputEventKey::get_location);
ClassDB::bind_method(D_METHOD("set_echo", "echo"), &InputEventKey::set_echo);
ClassDB::bind_method(D_METHOD("get_keycode_with_modifiers"), &InputEventKey::get_keycode_with_modifiers);
@ -603,12 +643,14 @@ void InputEventKey::_bind_methods() {
ClassDB::bind_method(D_METHOD("as_text_keycode"), &InputEventKey::as_text_keycode);
ClassDB::bind_method(D_METHOD("as_text_physical_keycode"), &InputEventKey::as_text_physical_keycode);
ClassDB::bind_method(D_METHOD("as_text_key_label"), &InputEventKey::as_text_key_label);
ClassDB::bind_method(D_METHOD("as_text_location"), &InputEventKey::as_text_location);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed");
ADD_PROPERTY(PropertyInfo(Variant::INT, "keycode"), "set_keycode", "get_keycode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "physical_keycode"), "set_physical_keycode", "get_physical_keycode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "key_label"), "set_key_label", "get_key_label");
ADD_PROPERTY(PropertyInfo(Variant::INT, "unicode"), "set_unicode", "get_unicode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "location", PROPERTY_HINT_ENUM, "Unspecified,Left,Right"), "set_location", "get_location");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "echo"), "set_echo", "is_echo");
}

View file

@ -157,6 +157,7 @@ class InputEventKey : public InputEventWithModifiers {
Key physical_keycode = Key::NONE;
Key key_label = Key::NONE;
uint32_t unicode = 0; ///unicode
KeyLocation location = KeyLocation::UNSPECIFIED;
bool echo = false; /// true if this is an echo key
@ -178,6 +179,9 @@ public:
void set_unicode(char32_t p_unicode);
char32_t get_unicode() const;
void set_location(KeyLocation p_key_location);
KeyLocation get_location() const;
void set_echo(bool p_enable);
virtual bool is_echo() const override;
@ -193,6 +197,7 @@ public:
virtual String as_text_physical_keycode() const;
virtual String as_text_keycode() const;
virtual String as_text_key_label() const;
virtual String as_text_location() const;
virtual String as_text() const override;
virtual String to_string() override;

View file

@ -260,6 +260,12 @@ enum class KeyModifierMask {
GROUP_SWITCH = (1 << 30)
};
enum class KeyLocation {
UNSPECIFIED,
LEFT,
RIGHT
};
// To avoid having unnecessary operators, only define the ones that are needed.
constexpr Key operator-(uint32_t a, Key b) {

View file

@ -176,6 +176,7 @@ VARIANT_ENUM_CAST(Variant::Operator);
VARIANT_ENUM_CAST(Key);
VARIANT_BITFIELD_CAST(KeyModifierMask);
VARIANT_ENUM_CAST(KeyLocation);
static inline Key &operator|=(Key &a, BitField<KeyModifierMask> b) {
a = static_cast<Key>(static_cast<int>(a) | static_cast<int>(b.operator int64_t()));

View file

@ -502,6 +502,7 @@ public:
VARIANT_ENUM_CLASS_CONSTRUCTOR(JoyAxis)
VARIANT_ENUM_CLASS_CONSTRUCTOR(JoyButton)
VARIANT_ENUM_CLASS_CONSTRUCTOR(Key)
VARIANT_ENUM_CLASS_CONSTRUCTOR(KeyLocation)
VARIANT_ENUM_CLASS_CONSTRUCTOR(MIDIMessage)
VARIANT_ENUM_CLASS_CONSTRUCTOR(MouseButton)

View file

@ -2374,6 +2374,16 @@
<constant name="KEY_MASK_GROUP_SWITCH" value="1073741824" enum="KeyModifierMask" is_bitfield="true">
Group Switch key mask.
</constant>
<constant name="KEY_LOCATION_UNSPECIFIED" value="0" enum="KeyLocation">
Used for keys which only appear once, or when a comparison doesn't need to differentiate the [code]LEFT[/code] and [code]RIGHT[/code] versions.
For example, when using [method InputEvent.is_match], an event which has [constant KEY_LOCATION_UNSPECIFIED] will match any [enum KeyLocation] on the passed event.
</constant>
<constant name="KEY_LOCATION_LEFT" value="1" enum="KeyLocation">
A key which is to the left of its twin.
</constant>
<constant name="KEY_LOCATION_RIGHT" value="2" enum="KeyLocation">
A key which is to the right of its twin.
</constant>
<constant name="MOUSE_BUTTON_NONE" value="0" enum="MouseButton">
Enum value which doesn't correspond to any mouse button. This is used to initialize [enum MouseButton] properties with a generic state.
</constant>

View file

@ -24,6 +24,12 @@
Returns a [String] representation of the event's [member keycode] and modifiers.
</description>
</method>
<method name="as_text_location" qualifiers="const">
<return type="String" />
<description>
Returns a [String] representation of the event's [member location]. This will be a blank string if the event is not specific to a location.
</description>
</method>
<method name="as_text_physical_keycode" qualifiers="const">
<return type="String" />
<description>
@ -77,6 +83,9 @@
+-----+ +-----+
[/codeblock]
</member>
<member name="location" type="int" setter="set_location" getter="get_location" enum="KeyLocation" default="0">
Represents the location of a key which has both left and right versions, such as [kbd]Shift[/kbd] or [kbd]Alt[/kbd].
</member>
<member name="physical_keycode" type="int" setter="set_physical_keycode" getter="get_physical_keycode" enum="Key" default="0">
Represents the physical location of a key on the 101/102-key US QWERTY keyboard, which corresponds to one of the [enum Key] constants.
To get a human-readable representation of the [InputEventKey], use [method OS.get_keycode_string] in combination with [method DisplayServer.keyboard_get_keycode_from_physical]:

View file

@ -79,7 +79,11 @@ String EventListenerLineEdit::get_event_text(const Ref<InputEvent> &p_event, boo
if (!text.is_empty()) {
text += " " + TTR("or") + " ";
}
text += mods_text + keycode_get_string(key->get_physical_keycode()) + " (" + TTR("Physical") + ")";
text += mods_text + keycode_get_string(key->get_physical_keycode()) + " (" + TTR("Physical");
if (key->get_location() != KeyLocation::UNSPECIFIED) {
text += " " + key->as_text_location();
}
text += ")";
}
if (key->get_key_label() != Key::NONE) {
if (!text.is_empty()) {

View file

@ -64,6 +64,7 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, c
bool show_mods = false;
bool show_device = false;
bool show_key = false;
bool show_location = false;
if (mod.is_valid()) {
show_mods = true;
@ -77,12 +78,17 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, c
if (k.is_valid()) {
show_key = true;
if (k->get_keycode() == Key::NONE && k->get_physical_keycode() == Key::NONE && k->get_key_label() != Key::NONE) {
Key phys_key = k->get_physical_keycode();
if (k->get_keycode() == Key::NONE && phys_key == Key::NONE && k->get_key_label() != Key::NONE) {
key_mode->select(KEYMODE_UNICODE);
} else if (k->get_keycode() != Key::NONE) {
key_mode->select(KEYMODE_KEYCODE);
} else if (k->get_physical_keycode() != Key::NONE) {
} else if (phys_key != Key::NONE) {
key_mode->select(KEYMODE_PHY_KEYCODE);
if (phys_key == Key::SHIFT || phys_key == Key::CTRL || phys_key == Key::ALT || phys_key == Key::META) {
key_location->select((int)k->get_location());
show_location = true;
}
} else {
// Invalid key.
event = Ref<InputEvent>();
@ -103,6 +109,7 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, c
mod_container->set_visible(show_mods);
device_container->set_visible(show_device);
key_mode->set_visible(show_key);
location_container->set_visible(show_location);
additional_options_container->show();
// Update mode selector based on original key event.
@ -240,6 +247,9 @@ void InputEventConfigurationDialog::_on_listen_input_changed(const Ref<InputEven
k->set_physical_keycode(Key::NONE);
k->set_keycode(Key::NONE);
}
if (key_location->get_selected_id() == (int)KeyLocation::UNSPECIFIED) {
k->set_location(KeyLocation::UNSPECIFIED);
}
}
Ref<InputEventWithModifiers> mod = received_event;
@ -433,6 +443,17 @@ void InputEventConfigurationDialog::_key_mode_selected(int p_mode) {
_set_event(k, original_event);
}
void InputEventConfigurationDialog::_key_location_selected(int p_location) {
Ref<InputEventKey> k = event;
if (k.is_null()) {
return;
}
k->set_location((KeyLocation)p_location);
_set_event(k, original_event);
}
void InputEventConfigurationDialog::_input_list_item_selected() {
TreeItem *selected = input_list_tree->get_selected();
@ -594,6 +615,8 @@ void InputEventConfigurationDialog::popup_and_configure(const Ref<InputEvent> &p
// Select "All Devices" by default.
device_id_option->select(0);
// Also "all locations".
key_location->select(0);
}
if (!p_current_action_name.is_empty()) {
@ -726,5 +749,24 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() {
key_mode->hide();
additional_options_container->add_child(key_mode);
// Key Location Selection
location_container = memnew(HBoxContainer);
location_container->hide();
Label *location_label = memnew(Label);
location_label->set_text(TTR("Physical location"));
location_container->add_child(location_label);
key_location = memnew(OptionButton);
key_location->set_h_size_flags(Control::SIZE_EXPAND_FILL);
key_location->add_item(TTR("Any"), (int)KeyLocation::UNSPECIFIED);
key_location->add_item(TTR("Left"), (int)KeyLocation::LEFT);
key_location->add_item(TTR("Right"), (int)KeyLocation::RIGHT);
key_location->connect("item_selected", callable_mp(this, &InputEventConfigurationDialog::_key_location_selected));
location_container->add_child(key_location);
additional_options_container->add_child(location_container);
main_vbox->add_child(additional_options_container);
}

View file

@ -99,6 +99,9 @@ private:
OptionButton *key_mode = nullptr;
HBoxContainer *location_container = nullptr;
OptionButton *key_location = nullptr;
void _set_event(const Ref<InputEvent> &p_event, const Ref<InputEvent> &p_original_event, bool p_update_input_list_selection = true);
void _on_listen_input_changed(const Ref<InputEvent> &p_event);
void _on_listen_focus_changed();
@ -110,6 +113,7 @@ private:
void _mod_toggled(bool p_checked, int p_index);
void _autoremap_command_or_control_toggled(bool p_checked);
void _key_mode_selected(int p_mode);
void _key_location_selected(int p_location);
void _device_selection_changed(int p_option_button_index);
void _set_current_device(int p_device);

View file

@ -124,6 +124,7 @@ void AndroidInputHandler::process_key_event(int p_physical_keycode, int p_unicod
ev->set_physical_keycode(physical_keycode);
ev->set_key_label(fix_key_label(p_key_label, keycode));
ev->set_unicode(fix_unicode(unicode));
ev->set_location(godot_location_from_android_code(p_physical_keycode));
ev->set_pressed(p_pressed);
ev->set_echo(p_echo);

View file

@ -38,3 +38,12 @@ Key godot_code_from_android_code(unsigned int p_code) {
}
return Key::UNKNOWN;
}
KeyLocation godot_location_from_android_code(unsigned int p_code) {
for (int i = 0; android_godot_location_pairs[i].android_code != AKEYCODE_MAX; i++) {
if (android_godot_location_pairs[i].android_code == p_code) {
return android_godot_location_pairs[i].godot_code;
}
}
return KeyLocation::UNSPECIFIED;
}

View file

@ -177,4 +177,24 @@ static AndroidGodotCodePair android_godot_code_pairs[] = {
Key godot_code_from_android_code(unsigned int p_code);
// Key location determination.
struct AndroidGodotLocationPair {
unsigned int android_code = 0;
KeyLocation godot_code = KeyLocation::UNSPECIFIED;
};
static AndroidGodotLocationPair android_godot_location_pairs[] = {
{ AKEYCODE_ALT_LEFT, KeyLocation::LEFT },
{ AKEYCODE_ALT_RIGHT, KeyLocation::RIGHT },
{ AKEYCODE_SHIFT_LEFT, KeyLocation::LEFT },
{ AKEYCODE_SHIFT_RIGHT, KeyLocation::RIGHT },
{ AKEYCODE_CTRL_LEFT, KeyLocation::LEFT },
{ AKEYCODE_CTRL_RIGHT, KeyLocation::RIGHT },
{ AKEYCODE_META_LEFT, KeyLocation::LEFT },
{ AKEYCODE_META_RIGHT, KeyLocation::RIGHT },
{ AKEYCODE_MAX, KeyLocation::UNSPECIFIED }
};
KeyLocation godot_location_from_android_code(unsigned int p_code);
#endif // ANDROID_KEYS_UTILS_H

View file

@ -119,7 +119,7 @@ public:
// MARK: Keyboard
void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed);
void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location);
bool is_keyboard_active() const;
// MARK: Motion

View file

@ -247,7 +247,7 @@ void DisplayServerIOS::touches_canceled(int p_idx) {
// MARK: Keyboard
void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed) {
void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location) {
Ref<InputEventKey> ev;
ev.instantiate();
ev->set_echo(false);
@ -270,6 +270,7 @@ void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_ph
ev->set_key_label(p_unshifted);
ev->set_physical_keycode(p_physical);
ev->set_unicode(fix_unicode(p_char));
ev->set_location(p_location);
perform_event(ev);
}

View file

@ -41,6 +41,7 @@ class KeyMappingIOS {
public:
static void initialize();
static Key remap_key(CFIndex p_keycode);
static KeyLocation key_location(CFIndex p_keycode);
};
#endif // KEY_MAPPING_IOS_H

View file

@ -38,6 +38,7 @@ struct HashMapHasherKeys {
};
HashMap<CFIndex, Key, HashMapHasherKeys> keyusage_map;
HashMap<CFIndex, KeyLocation, HashMapHasherKeys> location_map;
void KeyMappingIOS::initialize() {
if (@available(iOS 13.4, *)) {
@ -172,6 +173,15 @@ void KeyMappingIOS::initialize() {
keyusage_map[0x029D] = Key::GLOBE; // "Globe" key on smart connector / Mac keyboard.
keyusage_map[UIKeyboardHIDUsageKeyboardLANG1] = Key::JIS_EISU;
keyusage_map[UIKeyboardHIDUsageKeyboardLANG2] = Key::JIS_KANA;
location_map[UIKeyboardHIDUsageKeyboardLeftAlt] = KeyLocation::LEFT;
location_map[UIKeyboardHIDUsageKeyboardRightAlt] = KeyLocation::RIGHT;
location_map[UIKeyboardHIDUsageKeyboardLeftControl] = KeyLocation::LEFT;
location_map[UIKeyboardHIDUsageKeyboardRightControl] = KeyLocation::RIGHT;
location_map[UIKeyboardHIDUsageKeyboardLeftShift] = KeyLocation::LEFT;
location_map[UIKeyboardHIDUsageKeyboardRightShift] = KeyLocation::RIGHT;
location_map[UIKeyboardHIDUsageKeyboardLeftGUI] = KeyLocation::LEFT;
location_map[UIKeyboardHIDUsageKeyboardRightGUI] = KeyLocation::RIGHT;
}
}
@ -184,3 +194,13 @@ Key KeyMappingIOS::remap_key(CFIndex p_keycode) {
}
return Key::NONE;
}
KeyLocation KeyMappingIOS::key_location(CFIndex p_keycode) {
if (@available(iOS 13.4, *)) {
const KeyLocation *location = location_map.getptr(p_keycode);
if (location) {
return *location;
}
}
return KeyLocation::UNSPECIFIED;
}

View file

@ -116,8 +116,8 @@
- (void)deleteText:(NSInteger)charactersToDelete {
for (int i = 0; i < charactersToDelete; i++) {
DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true);
DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false);
DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true, KeyLocation::UNSPECIFIED);
DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false, KeyLocation::UNSPECIFIED);
}
}
@ -137,8 +137,8 @@
key = Key::SPACE;
}
DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, true);
DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, false);
DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, true, KeyLocation::UNSPECIFIED);
DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, false, KeyLocation::UNSPECIFIED);
}
}

View file

@ -78,13 +78,15 @@
us = u32lbl[0];
}
KeyLocation location = KeyMappingIOS::key_location(press.key.keyCode);
if (!u32text.is_empty() && !u32text.begins_with("UIKey")) {
for (int i = 0; i < u32text.length(); i++) {
const char32_t c = u32text[i];
DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true);
DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true, location);
}
} else {
DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true);
DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true, location);
}
}
}
@ -110,7 +112,9 @@
us = u32lbl[0];
}
DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false);
KeyLocation location = KeyMappingIOS::key_location(press.key.keyCode);
DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false, location);
}
}
}

View file

@ -3501,6 +3501,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
bool keypress = xkeyevent->type == KeyPress;
Key keycode = KeyMappingX11::get_keycode(keysym_keycode);
Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);
KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode);
if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {
keycode -= 'a' - 'A';
@ -3538,6 +3539,8 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
k->set_unicode(fix_unicode(tmp[i]));
}
k->set_location(key_location);
k->set_echo(false);
if (k->get_keycode() == Key::BACKTAB) {
@ -3563,6 +3566,8 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
Key keycode = KeyMappingX11::get_keycode(keysym_keycode);
Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);
KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode);
/* Phase 3, obtain a unicode character from the keysym */
// KeyMappingX11 also translates keysym to unicode.
@ -3662,6 +3667,9 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
if (keypress) {
k->set_unicode(fix_unicode(unicode));
}
k->set_location(key_location);
k->set_echo(p_echo);
if (k->get_keycode() == Key::BACKTAB) {

View file

@ -1113,6 +1113,20 @@ void KeyMappingX11::initialize() {
xkeysym_unicode_map[0x13BD] = 0x0153;
xkeysym_unicode_map[0x13BE] = 0x0178;
xkeysym_unicode_map[0x20AC] = 0x20AC;
// Scancode to physical location map.
// Ctrl.
location_map[0x25] = KeyLocation::LEFT;
location_map[0x69] = KeyLocation::RIGHT;
// Shift.
location_map[0x32] = KeyLocation::LEFT;
location_map[0x3E] = KeyLocation::RIGHT;
// Alt.
location_map[0x40] = KeyLocation::LEFT;
location_map[0x6C] = KeyLocation::RIGHT;
// Meta.
location_map[0x85] = KeyLocation::LEFT;
location_map[0x86] = KeyLocation::RIGHT;
}
Key KeyMappingX11::get_keycode(KeySym p_keysym) {
@ -1173,3 +1187,11 @@ char32_t KeyMappingX11::get_unicode_from_keysym(KeySym p_keysym) {
}
return 0;
}
KeyLocation KeyMappingX11::get_location(unsigned int p_code) {
const KeyLocation *location = location_map.getptr(p_code);
if (location) {
return *location;
}
return KeyLocation::UNSPECIFIED;
}

View file

@ -54,6 +54,7 @@ class KeyMappingX11 {
static inline HashMap<unsigned int, Key, HashMapHasherKeys> scancode_map;
static inline HashMap<Key, unsigned int, HashMapHasherKeys> scancode_map_inv;
static inline HashMap<KeySym, char32_t, HashMapHasherKeys> xkeysym_unicode_map;
static inline HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map;
KeyMappingX11() {}
@ -64,6 +65,7 @@ public:
static unsigned int get_xlibcode(Key p_keysym);
static Key get_scancode(unsigned int p_code);
static char32_t get_unicode_from_keysym(KeySym p_keysym);
static KeyLocation get_location(unsigned int p_code);
};
#endif // KEY_MAPPING_X11_H

View file

@ -75,6 +75,7 @@ public:
Key physical_keycode = Key::NONE;
Key key_label = Key::NONE;
uint32_t unicode = 0;
KeyLocation location = KeyLocation::UNSPECIFIED;
};
struct WindowData {

View file

@ -477,6 +477,7 @@ void DisplayServerMacOS::_process_key_events() {
k->set_physical_keycode(ke.physical_keycode);
k->set_key_label(ke.key_label);
k->set_unicode(ke.unicode);
k->set_location(ke.location);
_push_input(k);
} else {
@ -505,6 +506,7 @@ void DisplayServerMacOS::_process_key_events() {
k->set_keycode(ke.keycode);
k->set_physical_keycode(ke.physical_keycode);
k->set_key_label(ke.key_label);
k->set_location(ke.location);
if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == Key::NONE) {
k->set_unicode(key_event_buffer[i + 1].unicode);

View file

@ -606,6 +606,7 @@
ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]);
ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true);
ke.unicode = 0;
ke.location = KeyMappingMacOS::translate_location([event keyCode]);
ke.raw = false;
ds->push_to_key_event_buffer(ke);
@ -671,6 +672,7 @@
ke.physical_keycode = KeyMappingMacOS::translate_key(key);
ke.key_label = KeyMappingMacOS::remap_key(key, mod, true);
ke.unicode = 0;
ke.location = KeyMappingMacOS::translate_location(key);
ds->push_to_key_event_buffer(ke);
}
@ -698,6 +700,7 @@
ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]);
ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true);
ke.unicode = 0;
ke.location = KeyMappingMacOS::translate_location([event keyCode]);
ke.raw = true;
ds->push_to_key_event_buffer(ke);

View file

@ -45,6 +45,7 @@ public:
static Key translate_key(unsigned int p_key);
static unsigned int unmap_key(Key p_key);
static Key remap_key(unsigned int p_key, unsigned int p_state, bool p_unicode);
static KeyLocation translate_location(unsigned int p_key);
// Mapping for menu shortcuts.
static String keycode_get_native_string(Key p_keycode);

View file

@ -46,6 +46,7 @@ HashSet<unsigned int> numpad_keys;
HashMap<unsigned int, Key, HashMapHasherKeys> keysym_map;
HashMap<Key, unsigned int, HashMapHasherKeys> keysym_map_inv;
HashMap<Key, char32_t, HashMapHasherKeys> keycode_map;
HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map;
void KeyMappingMacOS::initialize() {
numpad_keys.insert(0x41); //kVK_ANSI_KeypadDecimal
@ -321,6 +322,20 @@ void KeyMappingMacOS::initialize() {
keycode_map[Key::BAR] = '|';
keycode_map[Key::BRACERIGHT] = '}';
keycode_map[Key::ASCIITILDE] = '~';
// Keysym -> physical location.
// Ctrl.
location_map[0x3b] = KeyLocation::LEFT;
location_map[0x3e] = KeyLocation::RIGHT;
// Shift.
location_map[0x38] = KeyLocation::LEFT;
location_map[0x3c] = KeyLocation::RIGHT;
// Alt/Option.
location_map[0x3a] = KeyLocation::LEFT;
location_map[0x3d] = KeyLocation::RIGHT;
// Meta/Command (yes, right < left).
location_map[0x36] = KeyLocation::RIGHT;
location_map[0x37] = KeyLocation::LEFT;
}
bool KeyMappingMacOS::is_numpad_key(unsigned int p_key) {
@ -396,6 +411,15 @@ Key KeyMappingMacOS::remap_key(unsigned int p_key, unsigned int p_state, bool p_
}
}
// Translates a macOS keycode to a Godot key location.
KeyLocation KeyMappingMacOS::translate_location(unsigned int p_key) {
const KeyLocation *location = location_map.getptr(p_key);
if (location) {
return *location;
}
return KeyLocation::UNSPECIFIED;
}
String KeyMappingMacOS::keycode_get_native_string(Key p_keycode) {
const char32_t *key = keycode_map.getptr(p_keycode);
if (key) {

View file

@ -187,6 +187,7 @@ void DisplayServerWeb::_key_callback(const String &p_key_event_code, const Strin
Key keycode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), false);
Key scancode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), true);
KeyLocation location = dom_code2godot_key_location(p_key_event_code.utf8().get_data());
DisplayServerWeb::KeyEvent ke;
@ -197,6 +198,7 @@ void DisplayServerWeb::_key_callback(const String &p_key_event_code, const Strin
ke.physical_keycode = scancode;
ke.key_label = fix_key_label(c, keycode);
ke.unicode = fix_unicode(c);
ke.location = location;
ke.mod = p_modifiers;
if (ds->key_event_pos >= ds->key_event_buffer.size()) {
@ -1383,6 +1385,7 @@ void DisplayServerWeb::process_events() {
ev->set_physical_keycode(ke.physical_keycode);
ev->set_key_label(ke.key_label);
ev->set_unicode(ke.unicode);
ev->set_location(ke.location);
if (ke.raw) {
dom2godot_mod(ev, ke.mod, ke.keycode);
}

View file

@ -95,6 +95,7 @@ private:
Key physical_keycode = Key::NONE;
Key key_label = Key::NONE;
uint32_t unicode = 0;
KeyLocation location = KeyLocation::UNSPECIFIED;
int mod = 0;
};

View file

@ -223,3 +223,24 @@ Key dom_code2godot_scancode(EM_UTF8 const p_code[32], EM_UTF8 const p_key[32], b
return Key::NONE;
#undef DOM2GODOT
}
KeyLocation dom_code2godot_key_location(EM_UTF8 const p_code[32]) {
#define DOM2GODOT(m_str, m_godot_code) \
if (memcmp((const void *)m_str, (void *)p_code, strlen(m_str) + 1) == 0) { \
return KeyLocation::m_godot_code; \
}
DOM2GODOT("AltLeft", LEFT);
DOM2GODOT("AltRight", RIGHT);
DOM2GODOT("ControlLeft", LEFT);
DOM2GODOT("ControlRight", RIGHT);
DOM2GODOT("MetaLeft", LEFT);
DOM2GODOT("MetaRight", RIGHT);
DOM2GODOT("OSLeft", LEFT);
DOM2GODOT("OSRight", RIGHT);
DOM2GODOT("ShiftLeft", LEFT);
DOM2GODOT("ShiftRight", RIGHT);
return KeyLocation::UNSPECIFIED;
#undef DOM2GODOT
}

View file

@ -165,20 +165,26 @@ DisplayServer::WindowID DisplayServerWindows::_get_focused_window_or_popup() con
void DisplayServerWindows::_register_raw_input_devices(WindowID p_target_window) {
use_raw_input = true;
RAWINPUTDEVICE rid[1] = {};
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x02;
RAWINPUTDEVICE rid[2] = {};
rid[0].usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC
rid[0].usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE
rid[0].dwFlags = 0;
rid[1].usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC
rid[1].usUsage = 0x06; // HID_USAGE_GENERIC_KEYBOARD
rid[1].dwFlags = 0;
if (p_target_window != INVALID_WINDOW_ID && windows.has(p_target_window)) {
// Follow the defined window
rid[0].hwndTarget = windows[p_target_window].hWnd;
rid[1].hwndTarget = windows[p_target_window].hWnd;
} else {
// Follow the keyboard focus
rid[0].hwndTarget = 0;
rid[1].hwndTarget = 0;
}
if (RegisterRawInputDevices(rid, 1, sizeof(rid[0])) == FALSE) {
if (RegisterRawInputDevices(rid, 2, sizeof(rid[0])) == FALSE) {
// Registration failed.
use_raw_input = false;
}
@ -3141,7 +3147,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
} break;
case WM_INPUT: {
if (mouse_mode != MOUSE_MODE_CAPTURED || !use_raw_input) {
if (!use_raw_input) {
break;
}
@ -3159,7 +3165,32 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
RAWINPUT *raw = (RAWINPUT *)lpb;
if (raw->header.dwType == RIM_TYPEMOUSE) {
if (raw->header.dwType == RIM_TYPEKEYBOARD) {
if (raw->data.keyboard.VKey == VK_SHIFT) {
// If multiple Shifts are held down at the same time,
// Windows natively only sends a KEYUP for the last one to be released.
if (raw->data.keyboard.Flags & RI_KEY_BREAK) {
if (GetAsyncKeyState(VK_SHIFT) < 0) {
// A Shift is released, but another Shift is still held
ERR_BREAK(key_event_pos >= KEY_EVENT_BUFFER_SIZE);
KeyEvent ke;
ke.shift = false;
ke.alt = alt_mem;
ke.control = control_mem;
ke.meta = meta_mem;
ke.uMsg = WM_KEYUP;
ke.window_id = window_id;
ke.wParam = VK_SHIFT;
// data.keyboard.MakeCode -> 0x2A - left shift, 0x36 - right shift.
// Bit 30 -> key was previously down, bit 31 -> key is being released.
ke.lParam = raw->data.keyboard.MakeCode << 16 | 1 << 30 | 1 << 31;
key_event_buffer[key_event_pos++] = ke;
}
}
}
} else if (mouse_mode == MOUSE_MODE_CAPTURED && raw->header.dwType == RIM_TYPEMOUSE) {
Ref<InputEventMouseMotion> mm;
mm.instantiate();
@ -4164,6 +4195,7 @@ void DisplayServerWindows::_process_key_events() {
}
Key key_label = keycode;
Key physical_keycode = KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24));
KeyLocation location = KeyMappingWindows::get_location((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24));
static BYTE keyboard_state[256];
memset(keyboard_state, 0, 256);
@ -4190,6 +4222,7 @@ void DisplayServerWindows::_process_key_events() {
}
k->set_keycode(keycode);
k->set_physical_keycode(physical_keycode);
k->set_location(location);
k->set_key_label(key_label);
if (i + 1 < key_event_pos && key_event_buffer[i + 1].uMsg == WM_CHAR) {

View file

@ -46,6 +46,7 @@ HashMap<unsigned int, Key, HashMapHasherKeys> vk_map;
HashMap<unsigned int, Key, HashMapHasherKeys> scansym_map;
HashMap<Key, unsigned int, HashMapHasherKeys> scansym_map_inv;
HashMap<unsigned int, Key, HashMapHasherKeys> scansym_map_ext;
HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map;
void KeyMappingWindows::initialize() {
// VK_LBUTTON (0x01)
@ -380,6 +381,15 @@ void KeyMappingWindows::initialize() {
scansym_map_ext[0x6C] = Key::LAUNCHMAIL;
scansym_map_ext[0x6D] = Key::LAUNCHMEDIA;
scansym_map_ext[0x78] = Key::MEDIARECORD;
// Scancode to physical location map.
// Shift.
location_map[0x2A] = KeyLocation::LEFT;
location_map[0x36] = KeyLocation::RIGHT;
// Meta.
location_map[0x5B] = KeyLocation::LEFT;
location_map[0x5C] = KeyLocation::RIGHT;
// Ctrl and Alt must be handled differently.
}
Key KeyMappingWindows::get_keysym(unsigned int p_code) {
@ -424,3 +434,16 @@ bool KeyMappingWindows::is_extended_key(unsigned int p_code) {
p_code == VK_RIGHT ||
p_code == VK_DOWN;
}
KeyLocation KeyMappingWindows::get_location(unsigned int p_code, bool p_extended) {
// Right- ctrl and alt have the same scancode as left, but are in the extended keys.
const Key *key = scansym_map.getptr(p_code);
if (key && (*key == Key::CTRL || *key == Key::ALT)) {
return p_extended ? KeyLocation::RIGHT : KeyLocation::LEFT;
}
const KeyLocation *location = location_map.getptr(p_code);
if (location) {
return *location;
}
return KeyLocation::UNSPECIFIED;
}

View file

@ -47,6 +47,7 @@ public:
static unsigned int get_scancode(Key p_keycode);
static Key get_scansym(unsigned int p_code, bool p_extended);
static bool is_extended_key(unsigned int p_code);
static KeyLocation get_location(unsigned int p_code, bool p_extended);
};
#endif // KEY_MAPPING_WINDOWS_H

View file

@ -85,6 +85,16 @@ TEST_CASE("[InputEventKey] Key correctly stores and retrieves unicode") {
CHECK(key.get_unicode() != 'y');
}
TEST_CASE("[InputEventKey] Key correctly stores and retrieves location") {
InputEventKey key;
CHECK(key.get_location() == KeyLocation::UNSPECIFIED);
key.set_location(KeyLocation::LEFT);
CHECK(key.get_location() == KeyLocation::LEFT);
CHECK(key.get_location() != KeyLocation::RIGHT);
}
TEST_CASE("[InputEventKey] Key correctly stores and checks echo") {
InputEventKey key;
@ -144,32 +154,36 @@ TEST_CASE("[InputEventKey] Key correctly converts itself to text") {
TEST_CASE("[InputEventKey] Key correctly converts its state to a string representation") {
InputEventKey none_key;
CHECK(none_key.to_string() == "InputEventKey: keycode=(Unset), mods=none, physical=false, pressed=false, echo=false");
CHECK(none_key.to_string() == "InputEventKey: keycode=(Unset), mods=none, physical=false, location=unspecified, pressed=false, echo=false");
// Set physical key to Escape.
none_key.set_physical_keycode(Key::ESCAPE);
CHECK(none_key.to_string() == "InputEventKey: keycode=4194305 (Escape), mods=none, physical=true, pressed=false, echo=false");
CHECK(none_key.to_string() == "InputEventKey: keycode=4194305 (Escape), mods=none, physical=true, location=unspecified, pressed=false, echo=false");
InputEventKey key;
// Set physical to None, set keycode to Space.
key.set_keycode(Key::SPACE);
CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, pressed=false, echo=false");
CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=unspecified, pressed=false, echo=false");
// Set location
key.set_location(KeyLocation::RIGHT);
CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=right, pressed=false, echo=false");
// Set pressed to true.
key.set_pressed(true);
CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, pressed=true, echo=false");
CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=right, pressed=true, echo=false");
// set echo to true.
key.set_echo(true);
CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, pressed=true, echo=true");
CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=right, pressed=true, echo=true");
// Press Ctrl and Alt.
key.set_ctrl_pressed(true);
key.set_alt_pressed(true);
#ifdef MACOS_ENABLED
CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Option, physical=false, pressed=true, echo=true");
CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Option, physical=false, location=right, pressed=true, echo=true");
#else
CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Alt, physical=false, pressed=true, echo=true");
CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Alt, physical=false, location=right, pressed=true, echo=true");
#endif
}
@ -291,6 +305,34 @@ TEST_CASE("[IsMatch] Keys are correctly matched") {
CHECK(key2.is_match(match, true) == true);
CHECK(key2.is_match(no_match, true) == false);
// Physical key with location.
InputEventKey key3;
key3.set_keycode(Key::NONE);
key3.set_physical_keycode(Key::SHIFT);
Ref<InputEventKey> loc_ref = key.create_reference(Key::NONE);
loc_ref->set_keycode(Key::SHIFT);
loc_ref->set_physical_keycode(Key::SHIFT);
CHECK(key3.is_match(loc_ref, false) == true);
key3.set_location(KeyLocation::UNSPECIFIED);
CHECK(key3.is_match(loc_ref, false) == true);
loc_ref->set_location(KeyLocation::LEFT);
CHECK(key3.is_match(loc_ref, false) == true);
key3.set_location(KeyLocation::LEFT);
CHECK(key3.is_match(loc_ref, false) == true);
key3.set_location(KeyLocation::RIGHT);
CHECK(key3.is_match(loc_ref, false) == false);
// Keycode key with location.
key3.set_physical_keycode(Key::NONE);
key3.set_keycode(Key::SHIFT);
CHECK(key3.is_match(loc_ref, false) == true);
}
} // namespace TestInputEventKey