Browse Source

[macOS] Handle accelerator and click events of the global menu items separately.

bruvzg 3 years ago
parent
commit
b85a4c5d79

+ 48 - 22
doc/classes/DisplayServer.xml

@@ -148,9 +148,10 @@
 			<param index="0" name="menu_root" type="String" />
 			<param index="1" name="label" type="String" />
 			<param index="2" name="callback" type="Callable" />
-			<param index="3" name="tag" type="Variant" default="null" />
-			<param index="4" name="accelerator" type="int" enum="Key" default="0" />
-			<param index="5" name="index" type="int" default="-1" />
+			<param index="3" name="key_callback" type="Callable" />
+			<param index="4" name="tag" type="Variant" default="null" />
+			<param index="5" name="accelerator" type="int" enum="Key" default="0" />
+			<param index="6" name="index" type="int" default="-1" />
 			<description>
 				Adds a new checkable item with text [param label] to the global menu with ID [param menu_root].
 				Returns index of the inserted item, it's not guaranteed to be the same as [param index] value.
@@ -168,9 +169,10 @@
 			<param index="1" name="icon" type="Texture2D" />
 			<param index="2" name="label" type="String" />
 			<param index="3" name="callback" type="Callable" />
-			<param index="4" name="tag" type="Variant" default="null" />
-			<param index="5" name="accelerator" type="int" enum="Key" default="0" />
-			<param index="6" name="index" type="int" default="-1" />
+			<param index="4" name="key_callback" type="Callable" />
+			<param index="5" name="tag" type="Variant" default="null" />
+			<param index="6" name="accelerator" type="int" enum="Key" default="0" />
+			<param index="7" name="index" type="int" default="-1" />
 			<description>
 				Adds a new checkable item with text [param label] and icon [param icon] to the global menu with ID [param menu_root].
 				Returns index of the inserted item, it's not guaranteed to be the same as [param index] value.
@@ -188,9 +190,10 @@
 			<param index="1" name="icon" type="Texture2D" />
 			<param index="2" name="label" type="String" />
 			<param index="3" name="callback" type="Callable" />
-			<param index="4" name="tag" type="Variant" default="null" />
-			<param index="5" name="accelerator" type="int" enum="Key" default="0" />
-			<param index="6" name="index" type="int" default="-1" />
+			<param index="4" name="key_callback" type="Callable" />
+			<param index="5" name="tag" type="Variant" default="null" />
+			<param index="6" name="accelerator" type="int" enum="Key" default="0" />
+			<param index="7" name="index" type="int" default="-1" />
 			<description>
 				Adds a new item with text [param label] and icon [param icon] to the global menu with ID [param menu_root].
 				Returns index of the inserted item, it's not guaranteed to be the same as [param index] value.
@@ -208,9 +211,10 @@
 			<param index="1" name="icon" type="Texture2D" />
 			<param index="2" name="label" type="String" />
 			<param index="3" name="callback" type="Callable" />
-			<param index="4" name="tag" type="Variant" default="null" />
-			<param index="5" name="accelerator" type="int" enum="Key" default="0" />
-			<param index="6" name="index" type="int" default="-1" />
+			<param index="4" name="key_callback" type="Callable" />
+			<param index="5" name="tag" type="Variant" default="null" />
+			<param index="6" name="accelerator" type="int" enum="Key" default="0" />
+			<param index="7" name="index" type="int" default="-1" />
 			<description>
 				Adds a new radio-checkable item with text [param label] and icon [param icon] to the global menu with ID [param menu_root].
 				Returns index of the inserted item, it's not guaranteed to be the same as [param index] value.
@@ -228,9 +232,10 @@
 			<param index="0" name="menu_root" type="String" />
 			<param index="1" name="label" type="String" />
 			<param index="2" name="callback" type="Callable" />
-			<param index="3" name="tag" type="Variant" default="null" />
-			<param index="4" name="accelerator" type="int" enum="Key" default="0" />
-			<param index="5" name="index" type="int" default="-1" />
+			<param index="3" name="key_callback" type="Callable" />
+			<param index="4" name="tag" type="Variant" default="null" />
+			<param index="5" name="accelerator" type="int" enum="Key" default="0" />
+			<param index="6" name="index" type="int" default="-1" />
 			<description>
 				Adds a new item with text [param label] to the global menu with ID [param menu_root].
 				Returns index of the inserted item, it's not guaranteed to be the same as [param index] value.
@@ -249,9 +254,10 @@
 			<param index="2" name="max_states" type="int" />
 			<param index="3" name="default_state" type="int" />
 			<param index="4" name="callback" type="Callable" />
-			<param index="5" name="tag" type="Variant" default="null" />
-			<param index="6" name="accelerator" type="int" enum="Key" default="0" />
-			<param index="7" name="index" type="int" default="-1" />
+			<param index="5" name="key_callback" type="Callable" />
+			<param index="6" name="tag" type="Variant" default="null" />
+			<param index="7" name="accelerator" type="int" enum="Key" default="0" />
+			<param index="8" name="index" type="int" default="-1" />
 			<description>
 				Adds a new item with text [param labe] to the global menu with ID [param menu_root].
 				Contrarily to normal binary items, multistate items can have more than two states, as defined by [param max_states]. Each press or activate of the item will increase the state by one. The default value is defined by [param default_state].
@@ -270,9 +276,10 @@
 			<param index="0" name="menu_root" type="String" />
 			<param index="1" name="label" type="String" />
 			<param index="2" name="callback" type="Callable" />
-			<param index="3" name="tag" type="Variant" default="null" />
-			<param index="4" name="accelerator" type="int" enum="Key" default="0" />
-			<param index="5" name="index" type="int" default="-1" />
+			<param index="3" name="key_callback" type="Callable" />
+			<param index="4" name="tag" type="Variant" default="null" />
+			<param index="5" name="accelerator" type="int" enum="Key" default="0" />
+			<param index="6" name="index" type="int" default="-1" />
 			<description>
 				Adds a new radio-checkable item with text [param label] to the global menu with ID [param menu_root].
 				Returns index of the inserted item, it's not guaranteed to be the same as [param index] value.
@@ -384,6 +391,15 @@
 				[b]Note:[/b] This method is implemented on macOS.
 			</description>
 		</method>
+		<method name="global_menu_get_item_key_callback" qualifiers="const">
+			<return type="Callable" />
+			<param index="0" name="menu_root" type="String" />
+			<param index="1" name="idx" type="int" />
+			<description>
+				Returns the callback of the item accelerator at index [param idx].
+				[b]Note:[/b] This method is implemented on macOS.
+			</description>
+		</method>
 		<method name="global_menu_get_item_max_states" qualifiers="const">
 			<return type="int" />
 			<param index="0" name="menu_root" type="String" />
@@ -502,7 +518,7 @@
 			<param index="1" name="idx" type="int" />
 			<param index="2" name="callback" type="Callable" />
 			<description>
-				Sets the callback of the item at index [param idx]. Callback is emitted when an item is pressed or its accelerator is activated.
+				Sets the callback of the item at index [param idx]. Callback is emitted when an item is pressed.
 				[b]Note:[/b] This method is implemented on macOS.
 			</description>
 		</method>
@@ -557,6 +573,16 @@
 				[b]Note:[/b] This method is implemented on macOS.
 			</description>
 		</method>
+		<method name="global_menu_set_item_key_callback">
+			<return type="void" />
+			<param index="0" name="menu_root" type="String" />
+			<param index="1" name="idx" type="int" />
+			<param index="2" name="key_callback" type="Callable" />
+			<description>
+				Sets the callback of the item at index [param idx]. Callback is emitted when its accelerator is activated.
+				[b]Note:[/b] This method is implemented on macOS.
+			</description>
+		</method>
 		<method name="global_menu_set_item_max_states">
 			<return type="void" />
 			<param index="0" name="menu_root" type="String" />

+ 1 - 1
editor/editor_node.cpp

@@ -366,7 +366,7 @@ void EditorNode::_update_scene_tabs() {
 		scene_tabs->add_tab(disambiguated_scene_names[i] + (unsaved ? "(*)" : ""), icon);
 
 		if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) {
-			DisplayServer::get_singleton()->global_menu_add_item("_dock", editor_data.get_scene_title(i) + (unsaved ? "(*)" : ""), callable_mp(this, &EditorNode::_global_menu_scene), i);
+			DisplayServer::get_singleton()->global_menu_add_item("_dock", editor_data.get_scene_title(i) + (unsaved ? "(*)" : ""), callable_mp(this, &EditorNode::_global_menu_scene), Callable(), i);
 		}
 
 		if (show_rb && editor_data.get_scene_root_script(i).is_valid()) {

+ 1 - 1
editor/project_manager.cpp

@@ -1321,7 +1321,7 @@ void ProjectList::update_dock_menu() {
 				}
 				favs_added = 0;
 			}
-			DisplayServer::get_singleton()->global_menu_add_item("_dock", _projects[i].project_name + " ( " + _projects[i].path + " )", callable_mp(this, &ProjectList::_global_menu_open_project), i);
+			DisplayServer::get_singleton()->global_menu_add_item("_dock", _projects[i].project_name + " ( " + _projects[i].path + " )", callable_mp(this, &ProjectList::_global_menu_open_project), Callable(), i);
 			total_added++;
 		}
 	}

+ 2 - 0
platform/macos/SCsub

@@ -17,6 +17,8 @@ files = [
     "godot_window.mm",
     "key_mapping_macos.mm",
     "godot_main_macos.mm",
+    "godot_menu_delegate.mm",
+    "godot_menu_item.mm",
     "dir_access_macos.mm",
     "tts_macos.mm",
     "joypad_macos.cpp",

+ 10 - 7
platform/macos/display_server_macos.h

@@ -140,6 +140,7 @@ private:
 	int key_event_pos = 0;
 
 	id tts = nullptr;
+	id menu_delegate = nullptr;
 
 	Point2i im_selection;
 	String im_text;
@@ -227,14 +228,14 @@ public:
 	virtual bool has_feature(Feature p_feature) const override;
 	virtual String get_name() const override;
 
-	virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
-	virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
-	virtual int global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
-	virtual int global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
-	virtual int global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
-	virtual int global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
-	virtual int global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
 	virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override;
+	virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+	virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+	virtual int global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+	virtual int global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+	virtual int global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+	virtual int global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+	virtual int global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
 	virtual int global_menu_add_separator(const String &p_menu_root, int p_index = -1) override;
 
 	virtual int global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const override;
@@ -244,6 +245,7 @@ public:
 	virtual bool global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const override;
 	virtual bool global_menu_is_item_radio_checkable(const String &p_menu_root, int p_idx) const override;
 	virtual Callable global_menu_get_item_callback(const String &p_menu_root, int p_idx) const override;
+	virtual Callable global_menu_get_item_key_callback(const String &p_menu_root, int p_idx) const override;
 	virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx) const override;
 	virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx) const override;
 	virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const override;
@@ -259,6 +261,7 @@ public:
 	virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override;
 	virtual void global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override;
 	virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) override;
+	virtual void global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) override;
 	virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) override;
 	virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) override;
 	virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) override;

+ 60 - 25
platform/macos/display_server_macos.mm

@@ -31,6 +31,7 @@
 #include "display_server_macos.h"
 
 #include "godot_content_view.h"
+#include "godot_menu_delegate.h"
 #include "godot_menu_item.h"
 #include "godot_window.h"
 #include "godot_window_delegate.h"
@@ -95,6 +96,7 @@ NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) {
 		if (!submenu.has(p_menu_root)) {
 			NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]];
 			[n_menu setAutoenablesItems:NO];
+			[n_menu setDelegate:menu_delegate];
 			submenu[p_menu_root] = n_menu;
 		}
 		menu = submenu[p_menu_root];
@@ -754,7 +756,7 @@ NSMenuItem *DisplayServerMacOS::_menu_add_item(const String &p_menu_root, const
 	return nullptr;
 }
 
-int DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
 	_THREAD_SAFE_METHOD_
 
 	int out = -1;
@@ -762,6 +764,7 @@ int DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const St
 	if (menu_item) {
 		GodotMenuItem *obj = [[GodotMenuItem alloc] init];
 		obj->callback = p_callback;
+		obj->key_callback = p_key_callback;
 		obj->meta = p_tag;
 		obj->checkable_type = CHECKABLE_TYPE_NONE;
 		obj->max_states = 0;
@@ -772,7 +775,7 @@ int DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const St
 	return out;
 }
 
-int DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
 	_THREAD_SAFE_METHOD_
 
 	int out = -1;
@@ -780,6 +783,7 @@ int DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, co
 	if (menu_item) {
 		GodotMenuItem *obj = [[GodotMenuItem alloc] init];
 		obj->callback = p_callback;
+		obj->key_callback = p_key_callback;
 		obj->meta = p_tag;
 		obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
 		obj->max_states = 0;
@@ -790,7 +794,7 @@ int DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, co
 	return out;
 }
 
-int DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
 	_THREAD_SAFE_METHOD_
 
 	int out = -1;
@@ -798,6 +802,7 @@ int DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, con
 	if (menu_item) {
 		GodotMenuItem *obj = [[GodotMenuItem alloc] init];
 		obj->callback = p_callback;
+		obj->key_callback = p_key_callback;
 		obj->meta = p_tag;
 		obj->checkable_type = CHECKABLE_TYPE_NONE;
 		obj->max_states = 0;
@@ -817,7 +822,7 @@ int DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, con
 	return out;
 }
 
-int DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
 	_THREAD_SAFE_METHOD_
 
 	int out = -1;
@@ -825,6 +830,7 @@ int DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_roo
 	if (menu_item) {
 		GodotMenuItem *obj = [[GodotMenuItem alloc] init];
 		obj->callback = p_callback;
+		obj->key_callback = p_key_callback;
 		obj->meta = p_tag;
 		obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
 		obj->max_states = 0;
@@ -844,7 +850,7 @@ int DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_roo
 	return out;
 }
 
-int DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
 	_THREAD_SAFE_METHOD_
 
 	int out = -1;
@@ -852,6 +858,7 @@ int DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_ro
 	if (menu_item) {
 		GodotMenuItem *obj = [[GodotMenuItem alloc] init];
 		obj->callback = p_callback;
+		obj->key_callback = p_key_callback;
 		obj->meta = p_tag;
 		obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
 		obj->max_states = 0;
@@ -862,7 +869,7 @@ int DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_ro
 	return out;
 }
 
-int DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
 	_THREAD_SAFE_METHOD_
 
 	int out = -1;
@@ -870,6 +877,7 @@ int DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_me
 	if (menu_item) {
 		GodotMenuItem *obj = [[GodotMenuItem alloc] init];
 		obj->callback = p_callback;
+		obj->key_callback = p_key_callback;
 		obj->meta = p_tag;
 		obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
 		obj->max_states = 0;
@@ -889,30 +897,15 @@ int DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_me
 	return out;
 }
 
-int DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
 	_THREAD_SAFE_METHOD_
 
-	NSMenu *menu = _get_menu_root(p_menu_root);
 	int out = -1;
-	if (menu) {
-		String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
-		NSMenuItem *menu_item;
-		int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems];
-		if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) {
-			p_index = [menu numberOfItems];
-		} else if (p_index < 0) {
-			p_index = item_count;
-		} else {
-			if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-				p_index++;
-			}
-			p_index = CLAMP(p_index, 0, item_count);
-		}
-		menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
-		out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index;
-
+	NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
+	if (menu_item) {
 		GodotMenuItem *obj = [[GodotMenuItem alloc] init];
 		obj->callback = p_callback;
+		obj->key_callback = p_key_callback;
 		obj->meta = p_tag;
 		obj->checkable_type = CHECKABLE_TYPE_NONE;
 		obj->max_states = p_max_states;
@@ -1104,6 +1097,27 @@ Callable DisplayServerMacOS::global_menu_get_item_callback(const String &p_menu_
 	return Callable();
 }
 
+Callable DisplayServerMacOS::global_menu_get_item_key_callback(const String &p_menu_root, int p_idx) const {
+	_THREAD_SAFE_METHOD_
+
+	const NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		ERR_FAIL_COND_V(p_idx < 0, Callable());
+		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+			p_idx++;
+		}
+		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Callable());
+		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+		if (menu_item) {
+			GodotMenuItem *obj = [menu_item representedObject];
+			if (obj) {
+				return obj->key_callback;
+			}
+		}
+	}
+	return Callable();
+}
+
 Variant DisplayServerMacOS::global_menu_get_item_tag(const String &p_menu_root, int p_idx) const {
 	_THREAD_SAFE_METHOD_
 
@@ -1401,6 +1415,25 @@ void DisplayServerMacOS::global_menu_set_item_callback(const String &p_menu_root
 	}
 }
 
+void DisplayServerMacOS::global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) {
+	_THREAD_SAFE_METHOD_
+
+	NSMenu *menu = _get_menu_root(p_menu_root);
+	if (menu) {
+		ERR_FAIL_COND(p_idx < 0);
+		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+			p_idx++;
+		}
+		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+		if (menu_item) {
+			GodotMenuItem *obj = [menu_item representedObject];
+			ERR_FAIL_COND(!obj);
+			obj->key_callback = p_key_callback;
+		}
+	}
+}
+
 void DisplayServerMacOS::global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) {
 	_THREAD_SAFE_METHOD_
 
@@ -3477,6 +3510,8 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
 	[main_menu setSubmenu:apple_menu forItem:menu_item];
 	[main_menu setAutoenablesItems:NO];
 
+	menu_delegate = [[GodotMenuDelegate alloc] init];
+
 	//!!!!!!!!!!!!!!!!!!!!!!!!!!
 	//TODO - do Vulkan and OpenGL support checks, driver selection and fallback
 	rendering_driver = p_rendering_driver;

+ 44 - 0
platform/macos/godot_menu_delegate.h

@@ -0,0 +1,44 @@
+/*************************************************************************/
+/*  godot_menu_delegate.h                                                */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef GODOT_MENU_DELEGATE_H
+#define GODOT_MENU_DELEGATE_H
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+@interface GodotMenuDelegate : NSObject <NSMenuDelegate> {
+}
+
+- (void)doNothing:(id)sender;
+
+@end
+
+#endif // GODOT_MENU_DELEGATE_H

+ 87 - 0
platform/macos/godot_menu_delegate.mm

@@ -0,0 +1,87 @@
+/*************************************************************************/
+/*  godot_menu_delegate.mm                                               */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "godot_menu_delegate.h"
+
+#include "display_server_macos.h"
+#include "godot_menu_item.h"
+#include "key_mapping_macos.h"
+
+@implementation GodotMenuDelegate
+
+- (void)doNothing:(id)sender {
+}
+
+- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action {
+	NSString *ev_key = [[event charactersIgnoringModifiers] lowercaseString];
+	NSUInteger ev_modifiers = [event modifierFlags] & NSDeviceIndependentModifierFlagsMask;
+	for (int i = 0; i < [menu numberOfItems]; i++) {
+		const NSMenuItem *menu_item = [menu itemAtIndex:i];
+		if ([menu_item isEnabled] && [[menu_item keyEquivalent] compare:ev_key] == NSOrderedSame) {
+			NSUInteger item_modifiers = [menu_item keyEquivalentModifierMask];
+
+			if (ev_modifiers == item_modifiers) {
+				GodotMenuItem *value = [menu_item representedObject];
+				if (value->key_callback != Callable()) {
+					// If custom callback is set, use it.
+					Variant tag = value->meta;
+					Variant *tagp = &tag;
+					Variant ret;
+					Callable::CallError ce;
+					value->key_callback.callp((const Variant **)&tagp, 1, ret, ce);
+				} else {
+					// Otherwise redirect event to the engine.
+					if (DisplayServer::get_singleton()) {
+						DisplayServerMacOS::KeyEvent ke;
+
+						ke.window_id = DisplayServer::MAIN_WINDOW_ID;
+						ke.macos_state = [event modifierFlags];
+						ke.pressed = true;
+						ke.echo = [event isARepeat];
+						ke.keycode = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags]);
+						ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]);
+						ke.raw = false;
+						ke.unicode = 0;
+
+						reinterpret_cast<DisplayServerMacOS *>(DisplayServer::get_singleton())->push_to_key_event_buffer(ke);
+					}
+				}
+
+				// Suppress default menu action.
+				*target = self;
+				*action = @selector(doNothing:);
+				return YES;
+			}
+		}
+	}
+	return NO;
+}
+
+@end

+ 1 - 3
platform/macos/godot_menu_item.h

@@ -45,6 +45,7 @@ enum GlobalMenuCheckType {
 @interface GodotMenuItem : NSObject {
 @public
 	Callable callback;
+	Callable key_callback;
 	Variant meta;
 	GlobalMenuCheckType checkable_type;
 	int max_states;
@@ -54,7 +55,4 @@ enum GlobalMenuCheckType {
 
 @end
 
-@implementation GodotMenuItem
-@end
-
 #endif // GODOT_MENU_ITEM_H

+ 34 - 0
platform/macos/godot_menu_item.mm

@@ -0,0 +1,34 @@
+/*************************************************************************/
+/*  godot_menu_item.mm                                                   */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "godot_menu_item.h"
+
+@implementation GodotMenuItem
+@end

+ 9 - 6
scene/gui/menu_bar.cpp

@@ -149,10 +149,6 @@ void MenuBar::_open_popup(int p_index, bool p_focus_item) {
 void MenuBar::shortcut_input(const Ref<InputEvent> &p_event) {
 	ERR_FAIL_COND(p_event.is_null());
 
-	if (is_native_menu()) {
-		return;
-	}
-
 	if (!_is_focus_owner_in_shortcut_context()) {
 		return;
 	}
@@ -253,7 +249,7 @@ void MenuBar::_update_submenu(const String &p_menu_name, PopupMenu *p_child) {
 			DisplayServer::get_singleton()->global_menu_add_submenu_item(p_menu_name, p_child->get_item_text(i), p_menu_name + "/" + itos(i));
 			_update_submenu(p_menu_name + "/" + itos(i), pm);
 		} else {
-			int index = DisplayServer::get_singleton()->global_menu_add_item(p_menu_name, p_child->get_item_text(i), callable_mp(p_child, &PopupMenu::activate_item), i);
+			int index = DisplayServer::get_singleton()->global_menu_add_item(p_menu_name, p_child->get_item_text(i), callable_mp(p_child, &PopupMenu::activate_item), Callable(), i);
 
 			if (p_child->is_item_checkable(i)) {
 				DisplayServer::get_singleton()->global_menu_set_item_checkable(p_menu_name, index, true);
@@ -376,12 +372,19 @@ void MenuBar::_notification(int p_what) {
 		case NOTIFICATION_INTERNAL_PROCESS: {
 			MutexLock lock(mutex);
 
+			if (is_native_menu()) {
+				// Handled by OS.
+				return;
+			}
+
 			Vector2 pos = DisplayServer::get_singleton()->mouse_get_position() - mouse_pos_adjusted - get_global_position();
 			int index = _get_index_at_point(pos);
 			if (index >= 0 && index != active_menu) {
 				selected_menu = index;
 				focused_menu = selected_menu;
-				get_menu_popup(active_menu)->hide();
+				if (active_menu >= 0) {
+					get_menu_popup(active_menu)->hide();
+				}
 				_open_popup(index);
 			}
 		} break;

+ 25 - 14
servers/display_server.cpp

@@ -44,37 +44,37 @@ DisplayServer::DisplayServerCreate DisplayServer::server_create_functions[Displa
 
 int DisplayServer::server_create_count = 1;
 
-int DisplayServer::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServer::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
 	WARN_PRINT("Global menus not supported by this display server.");
 	return -1;
 }
 
-int DisplayServer::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServer::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
 	WARN_PRINT("Global menus not supported by this display server.");
 	return -1;
 }
 
-int DisplayServer::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServer::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
 	WARN_PRINT("Global menus not supported by this display server.");
 	return -1;
 }
 
-int DisplayServer::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServer::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
 	WARN_PRINT("Global menus not supported by this display server.");
 	return -1;
 }
 
-int DisplayServer::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServer::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
 	WARN_PRINT("Global menus not supported by this display server.");
 	return -1;
 }
 
-int DisplayServer::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServer::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
 	WARN_PRINT("Global menus not supported by this display server.");
 	return -1;
 }
 
-int DisplayServer::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServer::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
 	WARN_PRINT("Global menus not supported by this display server.");
 	return -1;
 }
@@ -103,6 +103,10 @@ void DisplayServer::global_menu_set_item_callback(const String &p_menu_root, int
 	WARN_PRINT("Global menus not supported by this display server.");
 }
 
+void DisplayServer::global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) {
+	WARN_PRINT("Global menus not supported by this display server.");
+}
+
 bool DisplayServer::global_menu_is_item_checked(const String &p_menu_root, int p_idx) const {
 	WARN_PRINT("Global menus not supported by this display server.");
 	return false;
@@ -123,6 +127,11 @@ Callable DisplayServer::global_menu_get_item_callback(const String &p_menu_root,
 	return Callable();
 }
 
+Callable DisplayServer::global_menu_get_item_key_callback(const String &p_menu_root, int p_idx) const {
+	WARN_PRINT("Global menus not supported by this display server.");
+	return Callable();
+}
+
 Variant DisplayServer::global_menu_get_item_tag(const String &p_menu_root, int p_idx) const {
 	WARN_PRINT("Global menus not supported by this display server.");
 	return Variant();
@@ -527,14 +536,14 @@ void DisplayServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("has_feature", "feature"), &DisplayServer::has_feature);
 	ClassDB::bind_method(D_METHOD("get_name"), &DisplayServer::get_name);
 
-	ClassDB::bind_method(D_METHOD("global_menu_add_item", "menu_root", "label", "callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_item, DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
-	ClassDB::bind_method(D_METHOD("global_menu_add_check_item", "menu_root", "label", "callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_check_item, DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
-	ClassDB::bind_method(D_METHOD("global_menu_add_icon_item", "menu_root", "icon", "label", "callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_icon_item, DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
-	ClassDB::bind_method(D_METHOD("global_menu_add_icon_check_item", "menu_root", "icon", "label", "callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_icon_check_item, DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
-	ClassDB::bind_method(D_METHOD("global_menu_add_radio_check_item", "menu_root", "label", "callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_radio_check_item, DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
-	ClassDB::bind_method(D_METHOD("global_menu_add_icon_radio_check_item", "menu_root", "icon", "label", "callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_icon_radio_check_item, DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
-	ClassDB::bind_method(D_METHOD("global_menu_add_multistate_item", "menu_root", "labe", "max_states", "default_state", "callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_multistate_item, DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
 	ClassDB::bind_method(D_METHOD("global_menu_add_submenu_item", "menu_root", "label", "submenu", "index"), &DisplayServer::global_menu_add_submenu_item, DEFVAL(-1));
+	ClassDB::bind_method(D_METHOD("global_menu_add_item", "menu_root", "label", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
+	ClassDB::bind_method(D_METHOD("global_menu_add_check_item", "menu_root", "label", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_check_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
+	ClassDB::bind_method(D_METHOD("global_menu_add_icon_item", "menu_root", "icon", "label", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_icon_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
+	ClassDB::bind_method(D_METHOD("global_menu_add_icon_check_item", "menu_root", "icon", "label", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_icon_check_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
+	ClassDB::bind_method(D_METHOD("global_menu_add_radio_check_item", "menu_root", "label", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_radio_check_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
+	ClassDB::bind_method(D_METHOD("global_menu_add_icon_radio_check_item", "menu_root", "icon", "label", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_icon_radio_check_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
+	ClassDB::bind_method(D_METHOD("global_menu_add_multistate_item", "menu_root", "labe", "max_states", "default_state", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_multistate_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
 	ClassDB::bind_method(D_METHOD("global_menu_add_separator", "menu_root", "index"), &DisplayServer::global_menu_add_separator, DEFVAL(-1));
 
 	ClassDB::bind_method(D_METHOD("global_menu_get_item_index_from_text", "menu_root", "text"), &DisplayServer::global_menu_get_item_index_from_text);
@@ -544,6 +553,7 @@ void DisplayServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("global_menu_is_item_checkable", "menu_root", "idx"), &DisplayServer::global_menu_is_item_checkable);
 	ClassDB::bind_method(D_METHOD("global_menu_is_item_radio_checkable", "menu_root", "idx"), &DisplayServer::global_menu_is_item_radio_checkable);
 	ClassDB::bind_method(D_METHOD("global_menu_get_item_callback", "menu_root", "idx"), &DisplayServer::global_menu_get_item_callback);
+	ClassDB::bind_method(D_METHOD("global_menu_get_item_key_callback", "menu_root", "idx"), &DisplayServer::global_menu_get_item_key_callback);
 	ClassDB::bind_method(D_METHOD("global_menu_get_item_tag", "menu_root", "idx"), &DisplayServer::global_menu_get_item_tag);
 	ClassDB::bind_method(D_METHOD("global_menu_get_item_text", "menu_root", "idx"), &DisplayServer::global_menu_get_item_text);
 	ClassDB::bind_method(D_METHOD("global_menu_get_item_submenu", "menu_root", "idx"), &DisplayServer::global_menu_get_item_submenu);
@@ -559,6 +569,7 @@ void DisplayServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("global_menu_set_item_checkable", "menu_root", "idx", "checkable"), &DisplayServer::global_menu_set_item_checkable);
 	ClassDB::bind_method(D_METHOD("global_menu_set_item_radio_checkable", "menu_root", "idx", "checkable"), &DisplayServer::global_menu_set_item_radio_checkable);
 	ClassDB::bind_method(D_METHOD("global_menu_set_item_callback", "menu_root", "idx", "callback"), &DisplayServer::global_menu_set_item_callback);
+	ClassDB::bind_method(D_METHOD("global_menu_set_item_key_callback", "menu_root", "idx", "key_callback"), &DisplayServer::global_menu_set_item_key_callback);
 	ClassDB::bind_method(D_METHOD("global_menu_set_item_tag", "menu_root", "idx", "tag"), &DisplayServer::global_menu_set_item_tag);
 	ClassDB::bind_method(D_METHOD("global_menu_set_item_text", "menu_root", "idx", "text"), &DisplayServer::global_menu_set_item_text);
 	ClassDB::bind_method(D_METHOD("global_menu_set_item_submenu", "menu_root", "idx", "submenu"), &DisplayServer::global_menu_set_item_submenu);

+ 9 - 7
servers/display_server.h

@@ -128,14 +128,14 @@ public:
 	virtual bool has_feature(Feature p_feature) const = 0;
 	virtual String get_name() const = 0;
 
-	virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
-	virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
-	virtual int global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
-	virtual int global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
-	virtual int global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
-	virtual int global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
-	virtual int global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
 	virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1);
+	virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
+	virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
+	virtual int global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
+	virtual int global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
+	virtual int global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
+	virtual int global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
+	virtual int global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
 	virtual int global_menu_add_separator(const String &p_menu_root, int p_index = -1);
 
 	virtual int global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const;
@@ -145,6 +145,7 @@ public:
 	virtual bool global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const;
 	virtual bool global_menu_is_item_radio_checkable(const String &p_menu_root, int p_idx) const;
 	virtual Callable global_menu_get_item_callback(const String &p_menu_root, int p_idx) const;
+	virtual Callable global_menu_get_item_key_callback(const String &p_menu_root, int p_idx) const;
 	virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx) const;
 	virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx) const;
 	virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const;
@@ -160,6 +161,7 @@ public:
 	virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable);
 	virtual void global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable);
 	virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback);
+	virtual void global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback);
 	virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag);
 	virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text);
 	virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu);