Skip to content

Commit

Permalink
Add InputEventKey.location to tell left from right
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
romlok committed Jan 26, 2024
1 parent 4b6ad34 commit 8406e60
Show file tree
Hide file tree
Showing 35 changed files with 397 additions and 26 deletions.
4 changes: 4 additions & 0 deletions core/core_constants.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
44 changes: 43 additions & 1 deletion core/input/input_event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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);
Expand All @@ -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");
}

Expand Down
5 changes: 5 additions & 0 deletions core/input/input_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -178,6 +179,9 @@ class InputEventKey : public InputEventWithModifiers {
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;

Expand All @@ -193,6 +197,7 @@ class InputEventKey : public InputEventWithModifiers {
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;

Expand Down
6 changes: 6 additions & 0 deletions core/os/keyboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions core/variant/binder_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down
1 change: 1 addition & 0 deletions core/variant/variant.h
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ class Variant {
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)

Expand Down
10 changes: 10 additions & 0 deletions doc/classes/@GlobalScope.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
9 changes: 9 additions & 0 deletions doc/classes/InputEventKey.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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]:
Expand Down
6 changes: 5 additions & 1 deletion editor/event_listener_line_edit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
46 changes: 44 additions & 2 deletions editor/input_event_configuration_dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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>();
Expand All @@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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);
}
4 changes: 4 additions & 0 deletions editor/input_event_configuration_dialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ class InputEventConfigurationDialog : public ConfirmationDialog {

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();
Expand All @@ -110,6 +113,7 @@ class InputEventConfigurationDialog : public ConfirmationDialog {
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);
Expand Down
1 change: 1 addition & 0 deletions platform/android/android_input_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
9 changes: 9 additions & 0 deletions platform/android/android_keys_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
20 changes: 20 additions & 0 deletions platform/android/android_keys_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion platform/ios/display_server_ios.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class DisplayServerIOS : public DisplayServer {

// 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
Expand Down
Loading

0 comments on commit 8406e60

Please sign in to comment.